You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题原因

  1. 无用唤醒引发线程调度异常:原代码中,无论当前线程是否处理了num,都会调用notifyAll()然后wait(),导致大量无意义的线程唤醒与等待切换。在JDK11的线程调度机制下,这种频繁切换可能触发所有线程陷入等待、无法被正确调度的情况。
  2. 变量可见性隐患:num未声明为volatile,虽然synchronized块能保证可见性,但JDK11的锁优化(如偏向锁、轻量级锁)可能导致线程读取到旧值,引发逻辑判断异常。
  3. 锁竞争逻辑差异: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 16:02:01