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

Java多线程问题:实现两线程交替打印PingPong

多线程交替打印PingPong死锁问题分析与解决

死锁原因拆解

从你的输出日志可以明确看到死锁的触发点:

Thread 1 printed Ping
Thread 1 entered wait stage
Thread 2 printed Ping
Thread 2 entered wait stage
Thread 1 entered wait stage

Thread2打印Ping并通知后,Thread1被唤醒,但并未执行Pong逻辑,反而再次进入等待状态——此时Thread2已经处于等待,两个线程都陷入无限等待,形成死锁。具体原因通常是以下几点:

  • 等待条件未循环校验:如果代码中用if而非while判断执行条件,线程被唤醒后不会再次确认当前状态是否符合执行要求,一旦条件不满足就会错误地再次调用wait()。
  • 共享变量状态更新逻辑错误:lastExecutedThread的更新时机或规则不对,比如Thread2打印Ping后未正确更新该变量,导致Thread1唤醒后判断逻辑出错,认为自己仍不满足执行Pong的条件。
  • 缺乏阶段标记:仅靠lastExecutedThread无法区分当前处于Ping阶段还是Pong阶段,线程不知道自己该打印Ping还是Pong,逻辑混乱导致错误等待。

修正后的实现代码

以下是完全符合你需求的Java代码,通过明确的阶段标记、循环校验等待条件来避免死锁:

public class PingPongLoop {
    // volatile保证共享变量的跨线程可见性
    private static volatile int roundCount = 0;
    private static volatile String currentPhase = "THREAD1_PING"; // 标记当前执行阶段
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> runTask("Thread 1", "THREAD1_PING", "THREAD1_PONG"));
        Thread thread2 = new Thread(() -> runTask("Thread 2", "THREAD2_PING", "THREAD2_PONG"));

        thread1.start();
        thread2.start();
    }

    private static void runTask(String threadName, String pingPhase, String pongPhase) {
        synchronized (lock) {
            while (roundCount < 3) {
                // 循环等待,直到轮到当前线程的执行阶段
                while (!currentPhase.equals(pingPhase) && !currentPhase.equals(pongPhase)) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }

                // 执行打印逻辑,切换到下一个阶段
                if (currentPhase.equals(pingPhase)) {
                    System.out.printf("%s printed Ping%n", threadName);
                    currentPhase = threadName.equals("Thread 1") ? "THREAD2_PING" : "THREAD1_PONG";
                } else {
                    System.out.printf("%s printed Pong%n", threadName);
                    currentPhase = threadName.equals("Thread 1") ? "THREAD2_PONG" : "NEXT_ROUND";
                }
                System.out.printf("%s entered wait stage%n", threadName);
                lock.notifyAll();

                // 处理轮次切换:当Thread2完成Pong后,进入下一轮
                if (currentPhase.equals("NEXT_ROUND")) {
                    try {
                        roundCount++;
                        currentPhase = "THREAD1_PING";
                        lock.notifyAll();
                        lock.wait(); // 等待下一轮开始的信号
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
            }
        }
    }
}

关键优化点

  1. 循环校验等待条件:用while包裹wait(),确保线程被唤醒后会再次检查执行条件,避免虚假唤醒或状态变化导致的错误等待。
  2. 明确的阶段标记:通过currentPhase变量清晰标记当前需要执行的步骤,让线程明确自己该打印Ping还是Pong。
  3. 共享变量可见性:用volatile修饰roundCount和currentPhase,保证线程能及时读取到变量的最新值。
  4. 使用notifyAll():避免notify()只唤醒一个不满足执行条件的线程,导致其他线程永远等待。

内容的提问来源于stack exchange,提问作者user3153356

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 16:48:19