JDK1.8正常运行的多线程代码在JDK11中阻塞的原因咨询
JDK11下多线程阻塞问题分析
这段代码在JDK1.8中可正常输出0至99,但在JDK11中运行时会在输出2或3后阻塞:
public class Test { public static int num = 0; public static void main(String[] args) { new Thread(Test::printer, "t0").start(); new Thread(Test::printer, "t1").start(); new Thread(Test::printer, "t2").start(); } public static void printer() { synchronized (Test.class) { while (num < 100) { if (Thread.currentThread().getName().contains(String.valueOf(num % 3))) { System.out.println(Thread.currentThread().getName() + ": " + num++); } Test.class.notifyAll(); try { Test.class.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } Test.class.notifyAll(); } } }
通过jstack查看线程状态如下:
"Monitor Ctrl-Break" #21 daemon prio=5 os_prio=0 cpu=15.63ms elapsed=10.26s tid=0x000001ccc56cf800 nid=0x5424 runnable [0x000000a410efe000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(java.base@11.0.15/Native Method) at java.net.SocketInputStream.socketRead(java.base@11.0.15/SocketInputStream.java:115) at java.net.SocketInputStream.read(java.base@11.0.15/SocketInputStream.java:168) at java.net.SocketInputStream.read(java.base@11.0.15/SocketInputStream.java:140) at sun.nio.cs.StreamDecoder.readBytes(java.base@11.0.15/StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(java.base@11.0.15/StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(java.base@11.0.15/StreamDecoder.java:178) - locked <0x00000007181038c8> (a java.io.InputStreamReader) at java.io.InputStreamReader.read(java.base@11.0.15/InputStreamReader.java:181) at java.io.BufferedReader.fill(java.base@11.0.15/BufferedReader.java:161) at java.io.BufferedReader.readLine(java.base@11.0.15/BufferedReader.java:326) - locked <0x00000007181038c8> (a java.io.InputStreamReader) at java.io.BufferedReader.readLine(java.base@11.0.15/BufferedReader.java:392) at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:56) "t0" #22 prio=5 os_prio=0 cpu=0.00ms elapsed=10.25s tid=0x000001ccc56d0000 nid=0x6560 in Object.wait() [0x000000a4110fe000] java.lang.Thread.State: BLOCKED (on object monitor) at java.lang.Object.wait(java.base@11.0.15/Native Method) - waiting on <0x0000000718113e10> (a java.lang.Class for com.chen.Test) at java.lang.Object.wait(java.base@11.0.15/Object.java:328) at com.chen.Test.printer(Test.java:21) - waiting to re-lock in wait() <0x0000000718113e10> (a java.lang.Class for com.chen.Test) at com.chen.Test$$Lambda$14/0x0000000800066840.run(Unknown Source) at java.lang.Thread.run(java.base@11.0.15/Thread.java:834) "t1" #23 prio=5 os_prio=0 cpu=3718.75ms elapsed=10.25s tid=0x000001ccc56d1000 nid=0x7b8 in Object.wait() [0x000000a4111ff000] java.lang.Thread.State: BLOCKED (on object monitor) at java.lang.Object.wait(java.base@11.0.15/Native Method) - waiting on <no object reference available> at java.lang.Object.wait(java.base@11.0.15/Object.java:328) at com.chen.Test.printer(Test.java:21) - waiting to re-lock in wait() <0x0000000718113e10> (a java.lang.Class for com.chen.Test) at com.chen.Test$$Lambda$15/0x0000000800066c40.run(Unknown Source) at java.lang.Thread.run(java.base@11.0.15/Thread.java:834) "t2" #24 prio=5 os_prio=0 cpu=4140.63ms elapsed=10.25s tid=0x000001ccc56d2000 nid=0x6620 in Object.wait() [0x000000a4112ff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(java.base@11.0.15/Native Method) - waiting on <no object reference available> at java.lang.Object.wait(java.base@11.0.15/Object.java:328) at com.chen.Test.printer(Test.java:21) - waiting to re-lock in wait() <0x0000000718113e10> (a java.lang.Class for com.chen.Test) at com.chen.Test$$Lambda$16/0x0000000800066040.run(Unknown Source) at java.lang.Thread.run(java.base@11.0.15/Thread.java:834)
问题原因
- 无用唤醒引发线程调度异常:原代码中,无论当前线程是否处理了
num,都会调用notifyAll()然后wait(),导致大量无意义的线程唤醒与等待切换。在JDK11的线程调度机制下,这种频繁切换可能触发所有线程陷入等待、无法被正确调度的情况。 - 变量可见性隐患:
num未声明为volatile,虽然synchronized块能保证可见性,但JDK11的锁优化(如偏向锁、轻量级锁)可能导致线程读取到旧值,引发逻辑判断异常。 - 锁竞争逻辑差异:JDK11对
synchronized的实现进行了优化,线程从wait()唤醒后重新获取锁的逻辑与JDK1.8存在差异,频繁的无用唤醒可能触发锁竞争中的异常阻塞。
解决方法
调整代码逻辑,减少无用唤醒,并确保变量可见性:
public class Test { public static volatile int num = 0; // 声明为volatile,确保跨线程可见性 public static void main(String[] args) { new Thread(Test::printer, "t0").start(); new Thread(Test::printer, "t1").start(); new Thread(Test::printer, "t2").start(); } public static void printer() { synchronized (Test.class) { while (num < 100) { int currentMod = num % 3; // 不是当前线程的轮次,直接等待 if (!Thread.currentThread().getName().contains(String.valueOf(currentMod))) { try { Test.class.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } continue; } // 当前线程处理num,打印后递增 System.out.println(Thread.currentThread().getName() + ": " + num++); // 处理完成后再唤醒其他线程 Test.class.notifyAll(); } // 最后唤醒剩余等待的线程 Test.class.notifyAll(); } } }
修改后的逻辑:
- 仅当线程处理完
num后才唤醒其他线程,避免无意义的线程切换。 - 若不是当前线程的轮次,直接进入等待,减少锁竞争。
- 给
num加上volatile修饰,确保所有线程能读取到最新值。
内容的提问来源于stack exchange,提问作者Mr.Chen
相关产品推荐
相关产品推荐

