質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

6回答

1732閲覧

Javaでマルチスレッドを使用して該当文字を交互に表示したい。

K.M.PEANUTS

総合スコア40

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

1クリップ

投稿2021/12/13 04:16

編集2021/12/13 04:28

java

1public class PrintThread extends Thread { 2 3 private String message; 4 private Object lock = new Object(); 5 6 public PrintThread(String message){ 7 this.message = message; 8 } 9 10 public void run() { 11 for (int i=0 ; i<1000 ; i++){ 12 synchronized(lock) { 13 System.out.print(message); 14 } 15 } 16 } 17 18 public static void main(String[] args) { 19 PrintThread p1 = new PrintThread("*"); 20 p1.start(); 21 PrintThread p2 = new PrintThread("+"); 22 p2.start(); 23 } 24}

実行結果例↓(実行するたびに変わる)

txt

1*********************************************+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++************************++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++***********************************+*+*************************************************************************************************************************+***++++++++++++++*++++++++++++++**********************************************************************************************************************************+*+***************************************************************++++++++++++++++++++++++++++++++++++++++++++++++++*++*********************************************************************************************************************************************************************************************************************************************++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++***************************************++++++++++++++++++++++++++++++++++++++++++++++++***********************************+++++++**++**************++**++****+++******+++*****++++++**+++**++******++*****++**+++++***++++**++***++++++++++++++*********++***********+++**+++++++++**++******++*+++++++++++++***++++********++++++++****++**++**++++++**++************++**++****++**++++****++**++**++**++**++++++++++++++***++****++++++++**+++++********++++********+++++++++++++++***++++++**++**++*****++**++**++****+++++++++++++++++++++++++****+++++++++***++++*****+++++++++++++++******++******************++**++***************************************++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

上記のコードは、「*」と「+」を交互に表示するプログラムをしようと書いたコードです。しかし、synchronized文を上手く使うことができずに困っています。
このコードをどのように変えればよいか教えてください。理由も知りたいです。。

考察

おそらく現在のコードでは、フィールドのmessageが変わるスピードと画面表示のスピードが違うため思った通りの画面表示ができていないのだと考えています。
したがって、コンストラクタの「this.message = message」もsynchronized文の中に入れるようなコードが正しいと考えています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

y_waiwai

2021/12/13 04:22

提示のコードはどういうふうに実行されるんでしょうか。
dodox86

2021/12/13 04:35

> 「*」と「+」を交互に表示するプログラムをしようと書いたコードです。 なぜ、現状のコードで交互に表示すると思われたのでしょう。そういう制御はまったくされていません。
guest

回答6

0

2つのスレッドは、自分が notify されるまで wait する。
message を表示したら、別のスレッドに notify して、また wait する。

java

1public class PrintThread extends Thread { 2 3 private String message; 4 private Thread t; 5 6 public PrintThread(String message) { this.message = message; } 7 8 public void setThread(Thread t) { this.t = t; } 9 10 public synchronized void run() { 11 for (int i = 0 ; i < 1000; i++) { 12 try { wait(); } catch (InterruptedException e) { } 13 System.out.print(message); 14 synchronized(t) { t.notify(); } 15 } 16 } 17 18 public static void main(String[] args) { 19 PrintThread p1 = new PrintThread("*"); 20 PrintThread p2 = new PrintThread("+"); 21 p1.setThread(p2); 22 p2.setThread(p1); 23 p1.start(); 24 p2.start(); 25 try { Thread.sleep(100); } catch (InterruptedException e) { } 26 synchronized(p1) { p1.notify(); } 27 } 28}

追記
setThread が面倒なので、質問のコードにあったように
lockオブジェクトで同期を取ることにしました。

Java

1public class PrintThread extends Thread { 2 3 private String message; 4 private static Object lock = new Object(); 5 6 public PrintThread(String message) { this.message = message; } 7 8 public void run() { 9 for (int i = 0; i < 1000; i++) { 10 synchronized(lock) { 11 try { lock.wait(); } catch (InterruptedException e) { } 12 System.out.print(message); 13 lock.notify(); 14 } 15 } 16 } 17 18 public static void main(String[]args) { 19 PrintThread p1 = new PrintThread("*"); 20 p1.start(); 21 PrintThread p2 = new PrintThread("+"); 22 p2.start(); 23 try { Thread.sleep(100); } catch (InterruptedException e) { } 24 synchronized(lock) { lock.notify(); } 25 } 26}

追記2
xebme さんから指摘があり、何度も実行を繰り返して試してみたら、
*+ を 1000個出し終わる前に途中で止まってしまうことがありました。

やはり、自分のスレッドで wait して、相手のスレッドに notify する方が良いと思います。
でも、setThread は面倒なので、コンストラクタで次に実行するするスレッドの
番号を設定することにしました。

Java

1public class PrintThread extends Thread { 2 3 private String message; 4 private static Thread[] t = new Thread[2]; 5 private int next; 6 7 public PrintThread(String message, int next) { 8 this.message = message; 9 this.next = next; 10 } 11 12 public synchronized void run() { 13 for (int i = 0; i < 1000; i++) { 14 try { wait(); } catch (InterruptedException e) { } 15 System.out.print(message); 16 synchronized(t[next]) { t[next].notify(); } 17 } 18 } 19 20 public static void main(String[]args) { 21 t[0] = new PrintThread("*", 1); 22 t[0].start(); 23 t[1] = new PrintThread("+", 0); 24 t[1].start(); 25 try { Thread.sleep(100); } catch (InterruptedException e) { } 26 synchronized(t[0]) { t[0].notify(); } 27 } 28}

投稿2021/12/13 08:45

編集2021/12/15 04:26
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2021/12/15 03:50 編集

追記のコードは正しく動かないかもしれません。run()メソッド内のlock.notify()の後、2つのスレッドが平等にロックを獲得しようとします。必ずしも相手がロックを獲得するとは限りません。
xebme

2021/12/15 03:53

↑正しく動きます。自分がロックを獲得するとwait()でロックが外れ、相手がロックを獲得してwait() の後の文を実行します。
xebme

2021/12/15 07:05

↑正しく動きません。相手がロックを獲得してwait() の後の文を実行します。 自分はsyncronized(lock)で待ちます。ここでどちらか一方がwait()していなければならい条件が崩れます。相手がwait()の下の文を実行して、次のsynchronized(lock)に来るので、結局、両方ともwait()に入ります。
xebme

2021/12/15 07:10

notify()はすぐ有効になるわけではなく、synchronizedブロックを抜けたところで有効になります。理由は、synchronizedブロック内の更新結果をキャッシュからメモリに書き戻して、他のスレッドから見えるようにするから(と思っています)。
guest

0

Luice さんと同じ形の wait-notifyAll 版…といった所です。まだ回答に出てないようですので。
このまま 3 スレッド以上では動作できませんから notify で良いんですけども ^^;

java

1package teratail_java.q373551; 2 3public class PrintThread extends Thread { 4 private static String[] last = new String[1]; 5 private String message; 6 7 public PrintThread(String message) { 8 this.message = message; 9 } 10 11 public void run() { 12 try { 13 for(int i=0; i<10; i++) { 14 synchronized(last) { 15 while(last[0] == message) last.wait(); 16 17 System.out.print(message); 18 19 last[0] = message; 20 last.notifyAll(); 21 } 22 } 23 } catch(InterruptedException e) { 24 e.printStackTrace(); 25 } 26 } 27 28 public static void main(String[] args) { 29 PrintThread p1 = new PrintThread("*"); 30 p1.start(); 31 PrintThread p2 = new PrintThread("+"); 32 p2.start(); 33 } 34}

投稿2021/12/14 17:59

編集2021/12/14 18:01
jimbe

総合スコア12646

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2021/12/15 05:00 編集

まぁまぁ簡潔なのは良いんですが、やはり開始時のタイミングが実装依存ぽいのが怖いですねぇ・・・。
guest

0

lockオブジェクト

まだスレッドを利用する並行処理の理解ができていません。

  • lockオブジェクトが2つのスレッドで別々に生成されているので排他制御できません。
  • スレッドの実行は交互ではありません。次にどのスレッドが動くかは予想できないのです。

スケジューラ

交互に動かしたいなら、java.util.concurrent.CyclicBarrierを使うか、スケジューラを自作します。ここではスケジューラを自作してみましょう。

キューを使うラウンドロビンスケジューラ

wait()notify()/notifyAll()を使用してスレッドを同期します。スレッドは実行可能になったとき、キューの先頭をみて、自分自身ならば処理を行い、キューの先頭から末尾に自分のインスタンスを移動させます。キューの先頭が自分でなければ自分の番になるまでwaitします。

Java

1package q373551; 2 3import java.util.Deque; 4import java.util.LinkedList; 5 6public class RoundRobinScheduler { 7 8 final int repeat; 9 10 private boolean waitFor = true; 11 private final Deque<Thread> tqueue = new LinkedList<>(); 12 private int numThreads = 0; 13 14 public RoundRobinScheduler(int repeat) { 15 this.repeat = repeat; 16 } 17 18 class ScheduledTask extends Thread { 19 20 final Runnable r; 21 22 ScheduledTask(Runnable r) { 23 this.r = r; 24 } 25 26 @Override 27 public void run() { 28 29 Thread myThread = Thread.currentThread(); 30 31 try { 32 33 // 自分をラウンドロビンキューに登録、全てのスレッドが登録されるまで待つ 34 synchronized (tqueue) { 35 tqueue.addLast(myThread); 36 while(waitFor) { // スケジューラーが開始するまで待つ 37 try { 38 tqueue.wait(); 39 } catch (InterruptedException e) {e.printStackTrace();} 40 } 41 } 42 43 // 処理実行(repeatだけ繰り返す) 44 for (int i=0;i<repeat;i++) { 45 46 // 実行がスケジュールされるまで待つ 47 synchronized (tqueue) { 48 while (tqueue.peek() != myThread) { 49 try { 50 tqueue.wait(); 51 } catch (InterruptedException e) {e.printStackTrace();} 52 } 53 r.run(); // 処理実行 54 tqueue.addLast(tqueue.removeFirst()); //キューの末尾に自分を移動 55 tqueue.notifyAll(); 56 } 57 58 } 59 60 } finally { 61 synchronized (tqueue) { // 自身をキューから削除する 62 tqueue.remove(myThread); 63 numThreads--; 64 } 65 } 66 67 } 68 69 } 70 71 public void addTask(Runnable r) { 72 numThreads++; 73 new ScheduledTask(r).start(); 74 } 75 76 public void startTasks() { 77 synchronized (tqueue) { 78 while (tqueue.size() != numThreads) { // 登録がすべて終わるまで待つ 79 try { 80 tqueue.wait(100); 81 } catch (InterruptedException e) {e.printStackTrace();} 82 } 83 waitFor = false; 84 tqueue.notifyAll(); // スレッドを一斉に動かす 85 } 86 } 87 88 public void waitTasks() { 89 // 全てのタスクの終了を判定する 90 synchronized (tqueue) { 91 while (!tqueue.isEmpty()) { // すべて削除されるまで待つ 92 try { 93 tqueue.wait(100); 94 } catch (InterruptedException e) {e.printStackTrace();} 95 } 96 } 97 } 98 99} 100

テストコード

Java

1package q373551; 2 3public class TestScheduler { 4 5 public static void main(String[] args) { 6 7 final int repeat = 4; 8 RoundRobinScheduler s = new RoundRobinScheduler(repeat); 9 10 s.addTask(()->System.out.print("**********")); 11 s.addTask(()->System.out.print("++++++++++")); 12 s.addTask(()->System.out.print("----------")); 13 s.addTask(()->System.out.print("//////////")); 14 15 s.startTasks(); 16 s.waitTasks(); 17 18 System.out.println(); 19 System.out.println("done."); 20 21 } 22 23}

java.util.concurrent.CyclicBarrierの使い方はご自分で調べてください。タスクスケジューラなども調べてみてください。

投稿2021/12/13 10:44

編集2021/12/13 17:50
xebme

総合スコア1081

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2021/12/13 12:10

コードのおかしいところはあとでなおします。
xebme

2021/12/13 17:51

コードを1箇所変更しましたが、あとはこのままにしておきます。
guest

0

「*」と「+」を交互に表示する という順序性を担保するのであれば
前回と同じ文字を書こうとしたら待たせるようにすれば良いのではないでしょうか。

Java

1import java.util.concurrent.TimeUnit; 2 3public class PrintThread extends Thread { 4 5 /** 6 * 前回記述した文字列を保存する. 7 */ 8 private static String prev = ""; 9 10 private final String message; 11 12 public PrintThread(String message) { 13 this.message = message; 14 } 15 16 public void run() { 17 try { 18 for (int i = 0; i < 1000; i++) { 19 while (message.equals(prev)) { 20 TimeUnit.MILLISECONDS.sleep(1); 21 } 22 System.out.print(message); 23 prev = message; 24 } 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 30 public static void main(String[] args) { 31 PrintThread p1 = new PrintThread("*"); 32 p1.start(); 33 PrintThread p2 = new PrintThread("+"); 34 p2.start(); 35 } 36}

一応、実現可能ではありますが、シーケンシャルな処理をスレッドで行う意味は無いので
正直無駄な処理であると思います。

投稿2021/12/13 04:42

編集2021/12/13 04:45
Luice

総合スコア771

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

synchronizedは、特定のコードブロックを実行するのを同時に1スレッドに限るだけです。複数のスレッドがsynchronizedの実行をどのような順番に並べるかを制御するためのものではありません。

おまけに、lockをインスタンス変数としてしまっているので、2スレッドで別なlockが使われることとなり、「同時に1スレッドに限る」効果すら出ません。

「*」と「+」を交互に表示するプログラムをしようと書いたコードです。

このような、「特定の順番で実行しなければならないコード」をマルチスレッド化しようとしても、スレッド構築のコストがかかるだけでメリットがほぼありません。シングルスレッドでやりましょう。

投稿2021/12/13 04:41

maisumakun

総合スコア145184

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2021/12/13 04:42

> フィールドのmessageが変わるスピード 変わりません(スレッドごとに1つずつ存在します)。
guest

0

マルチスレッドによる実行は、いつ実行されるかってのは規定されません。
いつ実行されるか、どういう順序で実行されるかってのは誰にもわかりませんし、どうなっても文句は言えません

交互に実行させたいのであれば、同一スレッドで実行させましょう

投稿2021/12/13 04:30

y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問