前提・実現したいこと
マルチスレッドプログラミングの勉強をしておりますが、以下のコードで競合が発生する理由がわかりません。
発生している問題
後述のコードのMutexクラスのlock()で
Java
1 if (isLock)
だと競合が発生します(Gateクラスのcheck()で「***** BROKEN *****」を表示します)。
しかし、上述のif文を以下のようにwhile文にすると、競合が発生しません。
Java
1 while (isLock)
以下の理由からif文でも競合が起きないと考えていましたが、実際は競合が起きています。
- unlock()でnotify()メソッドを使って、1スレッドのみウェイトセットから出している
- lock()はsynchronizedをつけているためため、同時に1つのスレッドしか実行できない
→unlock()のnotify()によって起こされたスレッドか、新しくlock()を呼んだスレッドのみ
該当のソースコード
Java
1public class Main { 2 public static void main(String[] args) { 3 System.out.println("Testing Gate, hit CTRL+C to exit."); 4 Gate gate = new Gate(); 5 new UserThread(gate, "Alice", "Alaska").start(); 6 new UserThread(gate, "Bobby", "Brazil").start(); 7 new UserThread(gate, "Chris", "Canada").start(); 8 } 9} 10 11public class UserThread extends Thread { 12 private final Gate gate; 13 private final String myname; 14 private final String myaddress; 15 16 public UserThread(Gate gate, String myname, String myaddress) { 17 this.gate = gate; 18 this.myname = myname; 19 this.myaddress = myaddress; 20 } 21 22 @Override 23 public void run() { 24 System.out.println(myname + " BEGIN"); 25 while (true) { 26 gate.pass(myname, myaddress); 27 } 28 } 29} 30 31public class Gate { 32 private int counter = 0; 33 private String name = "Nobody"; 34 private String address = "Nowhere"; 35 private final Mutex mutex = new Mutex(); 36 37 public void pass(String name, String address) { 38 mutex.lock(); 39 try { 40 counter++; 41 this.name = name; 42 this.address = address; 43 check(); 44 } finally { 45 mutex.unlock(); 46 } 47 } 48 49 @Override 50 public String toString() { 51 String s = null; 52 53 mutex.lock(); 54 try { 55 s = "No." + counter + ": " + name + ", " + address; 56 } finally { 57 mutex.unlock(); 58 } 59 return s; 60 } 61 62 private void check() { 63 if (name.charAt(0) != address.charAt(0)) { 64 System.out.println("***** BROKEN ***** " + toString()); 65 } 66 } 67} 68 69public class Mutex { 70 private boolean isLock; 71 72 public synchronized void lock() { 73 try { 74 // 誰かがロック取得済の場合はウェイトセットに入れる 75 // while (isLock) { 76 if (isLock) { 77 wait(); 78 System.out.println("isLock=" + isLock); 79 } 80 81 isLock = true; 82 } catch (InterruptedException e) { 83 throw new RuntimeException(e); 84 } 85 } 86 87 public synchronized void unlock() { 88 // ウェイトセットの中から1スレッドのみ出す 89 notify(); 90 isLock = false; 91 } 92} 93
試したこと
jdbでMutexクラスの「System.out.println("isLock=" + isLock);」の行にブレークポイントをはり、他のスレッドの様子を確認したところ、lock()内に2つのスレッドがいました(以下のjdbのログのスレッド0x1beとスレッド0x1bf)。
スレッド0x1beはlock()の「if (isLock) {」の部分を、スレッド0x1bfはlock()の「System.out.println("isLock=" + isLock);」の部分を実行しているようです。
$ jdb Main jdbの初期化中... > stop in Mutex:10 遅延したブレークポイントMutex:10。 クラスがロードされた後に設定されます。 > run Thread-0[1] threads グループsystem: (java.lang.ref.Reference$ReferenceHandler)0x173 Reference Handlerは実行中です (java.lang.ref.Finalizer$FinalizerThread)0x174 Finalizer は条件を待機中です (java.lang.Thread)0x175 Signal Dispatcherは実行中です グループmain: (UserThread)0x1be Thread-0 はモニター内で待機中です (UserThread)0x1bf Thread-1 は実行中です(ブレークポイント) (UserThread)0x1c1 Thread-2 はモニター内で待機中です (java.lang.Thread)0x1c5 DestroyJavaVM は実行中です グループInnocuousThreadGroup: (jdk.internal.misc.InnocuousThread)0x1a2 Common-Cleaner は条件を待機中です Thread-0[1] thread 0x1be Thread-0[1] where [1] Mutex.lock (Mutex.java:8) [2] Gate.pass (Gate.java:9) [3] UserThread.run (UserThread.java:16) Thread-0[1] thread 0x1bf Thread-1[1] where [1] Mutex.lock (Mutex.java:10) [2] Gate.pass (Gate.java:9) [3] UserThread.run (UserThread.java:16) Thread-1[1]
~~
lock()の中に2つスレッドがいて、正常にロックを実装できていないことが競合が起きている原因だと思いますが、なぜsynchronizedメソッドの中に同時に複数のスレッドが入れるのかがわかりません。
新しくlock()を呼んだスレッドはもちろん、notify()によってウェイトセットから出てきたスレッドも、synchronizedによるロックの獲得待ちをするため、lock()には同時に1つのスレッドしか入れない認識です。
~~
2020.10.12追加
以下の記事は、Mutexクラスのlock()のif文をwhile文に修正した状態でプログラムを実行した場合のものです。
if文だと、そもそも競合状態になる場合があることがわかりました(quickquipさんの回答参照)。
jdbの表示の読み方が間違っていたようです。jdbのlockコマンドでロックに関する情報を確認したところ、lock()の中を実行中のスレッドは1つしかありませんでした。ロック獲得待ちをしているスレッドでwhereコマンドを実行した時に表示される行番号は、実行しようとしているメソッドの中でjdbがブレークポイントをはれる最初の行番号を表示するようです(「try {」やコメント行にはブレークポイントが設定できない。そのため、その次の行の「if (isLock) {」の行番号が表示される)。
Thread-0[1] threads グループsystem: (java.lang.ref.Reference$ReferenceHandler)0x173 Reference Handlerは実行中です (java.lang.ref.Finalizer$FinalizerThread)0x174 Finalizer は条件を待機中です (java.lang.Thread)0x175 Signal Dispatcherは実行中です グループmain: (UserThread)0x1be Thread-0 は実行中です(ブレークポイント) (UserThread)0x1bf Thread-1 はモニター内で待機中です (UserThread)0x1c1 Thread-2 はモニター内で待機中です (java.lang.Thread)0x1c6 DestroyJavaVM は実行中です グループInnocuousThreadGroup: (jdk.internal.misc.InnocuousThread)0x1a2 Common-Cleaner は条件を待機中です Thread-0[1] where [1] Mutex.lock (Mutex.java:10) [2] Gate.pass (Gate.java:9) [3] UserThread.run (UserThread.java:16) Thread-0[1] thread 0x1bf Thread-1[1] where [1] Mutex.lock (Mutex.java:8) [2] Gate.pass (Gate.java:9) [3] UserThread.run (UserThread.java:16) Thread-1[1] thread 0x1c1 Thread-2[1] where [1] Mutex.lock (Mutex.java:8) [2] Gate.pass (Gate.java:9) [3] UserThread.run (UserThread.java:16) // lockコマンドでthis(lock()がロックする時に使うインスタンス)のロックの情報を表示してやると、 // 実行中のスレッド(ロックを獲得しているスレッド)はThread-0のみで、Thread-1とThread-2は // ロック獲得待ちであることがわかる。 Thread-2[1] lock this com.sun.tools.example.debug.expr.ParseException: Unable to complete expression. Thread not suspended for method invoke 所有者: Thread-0、エントリ数: 1 スレッドを待機中: Thread-1 スレッドを待機中: Thread-2 Thread-2[1]
補足情報(FW/ツールのバージョンなど)
- OS
macOS Catalina バージョン10.15.7
- Javaのバージョン
$ java -version openjdk version "11.0.8" 2020-07-14 OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.8+10) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.8+10, mixed mode)
回答1件
あなたの回答
tips
プレビュー