このタイトルを書いているような気がします。目の肥えた人は、これを一見するとデッドロックだと思うかもしれません。しかし、今日説明されている状況は、真の意味でのデッドロックではなく、よくてもマクロの意味でのデッドロックです。この場合、デッドロック情報はjstackツールを使用して表示できません。
スレッドプールの不適切な使用により、スレッドが互いに待機する
今日の例
public class Test {
static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
public static void main(String[] args) throws ExecutionException, InterruptedException {
Future<String> outterFuture = threadPoolExecutor.submit(() -> { Future<String> innerFuture = threadPoolExecutor.submit(() -> { System.out.println("inner finish");
return "inner finish";
}); String s = innerFuture.get();
System.out.println("outter get inner finish:" + s);
System.out.println("outter finish");
return "outter finish";
}); String s = outterFuture.get();
System.out.println("process get outter finish:" + s);
}}
つまり、スレッド1が送信され、スレッド2がスレッド1で送信され、スレッド1はスレッド2の結果を待ちます。一部の人々は問題をはっきりと見るかもしれませんが、もちろんこれは単純化された結果であり、スレッドプールの実際の使用はこれよりも不可解かもしれません。このメソッドを実行すると、2つのスレッドが直接待機します。
jstack現象
2020-09-12 09:52:41
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):
"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007fbf38001000 nid=0x37c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"pool-1-thread-1" #10 prio=5 os_prio=0 tid=0x00007fbf9819c800 nid=0x7932 waiting on condition [0x00007fbf77af9000]
java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000006c8e08478> (a java.util.concurrent.FutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at Test.lambda$main$1(Test.java:24)
at Test$$Lambda$1/1418481495.call(Unknown Source)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x00007fbf980d2000 nid=0x7930 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"C1 CompilerThread3" #8 daemon prio=9 os_prio=0 tid=0x00007fbf980c7000 nid=0x792f waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"C2 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007fbf980c4800 nid=0x792e waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fbf980c3000 nid=0x792d waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fbf980c0000 nid=0x792c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fbf980be800 nid=0x792b runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fbf9808b800 nid=0x792a in Object.wait() [0x00007fbf84371000]
java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000006c8e01a60> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x00000006c8e01a60> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fbf98086800 nid=0x7929 in Object.wait() [0x00007fbf84472000]
java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000006c8e0f950> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000006c8e0f950> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"main" #1 prio=5 os_prio=0 tid=0x00007fbf98008800 nid=0x791e waiting on condition [0x00007fbf9e635000]
java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000006c8e177b8> (a java.util.concurrent.FutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at Test.main(Test.java:31)
"VM Thread" os_prio=0 tid=0x00007fbf9807f000 nid=0x7928 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fbf9801d800 nid=0x791f runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fbf9801f800 nid=0x7920 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007fbf98021800 nid=0x7921 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007fbf98023000 nid=0x7922 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00007fbf98025000 nid=0x7923 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00007fbf98027000 nid=0x7925 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00007fbf98028800 nid=0x7926 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00007fbf9802a800 nid=0x7927 runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007fbf980d5000 nid=0x7931 waiting on condition
JNI global references: 201
jstackを介して積極的にデッドロックが見つかりませんでした。ビジネスとコンポーネントには多くのスレッドがあるため、判断が難しくなります。
スレッドプールパラメータ分析
以下は、ほとんどのThreadPoolExecutorスレッドプールパラメーターを持つコンストラクターです。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...... }
関数のパラメータの意味は次のとおりです(詳細についてはBaiduを参照してください)。
- corePoolSize:スレッドプール内のコアスレッドの数
- maximumPoolSize:スレッドプールの最大数
- keepAliveTime:アイドルスレッドの生存時間
- 単位:時間単位
- workQueue:スレッドプールが使用するバッファキュー
- threadFactory:スレッドプールがスレッドを作成するために使用するファクトリ
- ハンドラー:拒否されたタスクに対するスレッドプールの処理戦略
原因分析1
例で定義されているコアスレッドの数と最大スレッド数はどちらも1です。つまり、スレッドプールで同時に実行できるスレッドは1つだけです。次に、実行するスレッドを格納するスレッドキューを定義します。問題は、スレッド外部が送信されると、スレッドがコアスレッド番号1を占有し、次にスレッド内部がスレッド外部で送信され、スレッド内部の実行結果を待機することです。スレッドプールで実行中のスレッドの現在の数が最大スレッド数を下回った後、スレッドインナーはキューで待機しているスレッドを待機する必要があるため、スレッドインナーは実行されませんでした。その結果、スレッドの外側は、スレッドプールが実行できるタスクの最大数を占め、スレッドの内側の結果を待機し、スレッドの内側は、結果を返さずにスレッドプールの実行を待機します。
原因分析2
実際、問題はjstackのログからも見つけることができます。たとえば、リファレンスハンドラーとファイナライザーという名前のスレッドでは、自発的な待機とロックの条件は同じです。つまり、自分自身を待機していて、待機しています。
デッドロック
デッドロックの状況を以下に示します。
デッドロック状態
- 相互に排他的に使用します。つまり、リソースが1つのスレッドによって使用(占有)されている場合、他のスレッドはそれを使用できません。
- プリエンプション以外の場合、リソースリクエスタはリソース所有者からリソースを強制的に取得できず、リソースはリソース所有者のみがアクティブに解放できます
- 要求と保持、つまり、リソースの要求者が他のリソースを要求しながら元のリソースを保持する場合
- 循環待機、つまり待機キューがあります。P1はP2のリソースを占有し、P2はP3のリソースを占有し、P3はP1のリソースを占有します。
デッドロックの例
public class DeadLock implements Runnable{
private static Object obj1 = new Object();
private static Object obj2 = new Object();
private boolean flag;
public DeadLock(boolean flag){
this.flag = flag;
} @Override
public void run(){
System.out.println(Thread.currentThread().getName() + "运行");
if(flag){
synchronized(obj1){
System.out.println(Thread.currentThread().getName() + "已经锁住obj1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace(); } synchronized(obj2){
// 执行不到这里
System.out.println("1秒钟后,"+Thread.currentThread().getName()
+ "锁住obj2");
}
}
}else{
synchronized(obj2){
System.out.println(Thread.currentThread().getName() + "已经锁住obj2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj1){
// 执行不到这里
System.out.println("1秒钟后,"+Thread.currentThread().getName()
+ "锁住obj1");
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLock(true), "线程1");
Thread t2 = new Thread(new DeadLock(false), "线程2");
t1.start();
t2.start();
}
}
jstack現象
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000003866000 nid=0x2ffc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"线程2" #12 prio=5 os_prio=0 tid=0x000000001e6b8000 nid=0x20e4 waiting for monitor entry [0x000000001f8bf000]
java.lang.Thread.State: BLOCKED (on object monitor) at com.wp.security.springboot.DeadLock.run(DeadLock.java:42)
- waiting to lock <0x000000076b47b980> (a java.lang.Object)
- locked <0x000000076b47b990> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"线程1" #11 prio=5 os_prio=0 tid=0x000000001eec8800 nid=0x11d8 waiting for monitor entry [0x000000001f7bf000]
java.lang.Thread.State: BLOCKED (on object monitor) at com.wp.security.springboot.DeadLock.run(DeadLock.java:28)
- waiting to lock <0x000000076b47b990> (a java.lang.Object)
- locked <0x000000076b47b980> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000001e607000 nid=0x3888 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001e57c800 nid=0x1a1c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001e56f000 nid=0x37b4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001e56e800 nid=0x1eb0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001e56a800 nid=0x2298 runnable [0x000000001e9be000]
java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076b4cf910> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076b4cf910> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001cf8a000 nid=0x1e84 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001cf74000 nid=0x2330 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001cf4e800 nid=0x4168 in Object.wait() [0x000000001e2bf000]
java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076b208ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x000000076b208ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:212)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000003956000 nid=0x3478 in Object.wait() [0x000000001e1bf000]
java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076b206bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076b206bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000000001cf27000 nid=0x47a4 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000000000387b800 nid=0x1ec8 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000000000387d000 nid=0x47a0 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000387e800 nid=0x3364 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000003881800 nid=0x4848 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001e5e5800 nid=0x1318 waiting on condition
JNI global references: 12
Found one Java-level deadlock:============================="线程2":
waiting to lock monitor 0x000000001cf4b598 (object 0x000000076b47b980, a java.lang.Object),
which is held by "线程1"
"线程1":
waiting to lock monitor 0x000000001cf4ded8 (object 0x000000076b47b990, a java.lang.Object),
which is held by "线程2"
Java stack information for the threads listed above:==================================================="线程2":
at com.wp.security.springboot.DeadLock.run(DeadLock.java:42)
- waiting to lock <0x000000076b47b980> (a java.lang.Object)
- locked <0x000000076b47b990> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"线程1":
at com.wp.security.springboot.DeadLock.run(DeadLock.java:28)
- waiting to lock <0x000000076b47b990> (a java.lang.Object)
- locked <0x000000076b47b980> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
ここでは、ロックの待機とスレッド1とスレッド2のリソースのロックを一目で確認できます。デッドロックを見つけるためのヒントもjstackの最後にありますJavaレベルのデッドロックを見つけました
jstackがデッドロックをアクティブに検出できないのはなぜですか
スレッドプールの例では、デッドロックがロックの占有によって引き起こされていることが明確ではないため、この例はデッドロックではありません。デッドロックの例は非常に明確です。これは、2つのスレッドが互いのロックを横取りしているために発生するため、これはデッドロックであり、デッドロックはjstackで見つかります。
デッドロックと同様に相互待機を判断する方法
このような状況では、jstackプロンプトなしでビジネスロジックのスレッドを分析して問題を見つけることは非常に困難です。私はこれら2つの例のスレッドダンプを比較して、「待機中」、「ロック待機中」、「パーキング待機中」、および「ロック済み」のキーワードに気づきました。Baiduで確認しました。
- 条件付きの待機とは、Object.wait以外の条件付き待機を意味します。たとえば、sleep、park、その他の操作を呼び出します
- 待機するパーキングはパークアクションを呼び出しています
- ロック待ちはロックオブジェクトを待っています
デッドロックの例では、jstackがデッドロックを検出できる理由は、ロックの待機とロックによって判断されると思います。これが本当のデッドロックです。そして、今日議論されているスレッドプールで待機しているスレッドの状況は、待機してロックされています。スレッドが互いに待機しているかどうか、およびデッドロックと同様のデッドロックを判断したい場合は、実際には、待機状態とロック状態がすべて同じかどうかを判断する必要があります。
この記事が少し役に立ったと感じたら、一緒に学び、上達することに集中してください〜
また、私のパブリックアカウントをフォローすることもできます。テクニカルドライグッズの記事と関連情報の共有があります。