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; } } } } } }
关键优化点
- 循环校验等待条件:用
while包裹wait(),确保线程被唤醒后会再次检查执行条件,避免虚假唤醒或状态变化导致的错误等待。 - 明确的阶段标记:通过
currentPhase变量清晰标记当前需要执行的步骤,让线程明确自己该打印Ping还是Pong。 - 共享变量可见性:用
volatile修饰roundCount和currentPhase,保证线程能及时读取到变量的最新值。 - 使用notifyAll():避免
notify()只唤醒一个不满足执行条件的线程,导致其他线程永远等待。
内容的提问来源于stack exchange,提问作者user3153356
相关产品推荐
相关产品推荐

