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

多线程应用JUnit测试后及重复运行时无法运行故障求助

排查多线程打印机计数器的间歇性失效问题

这种偶尔正常、偶尔线程永久休眠、JUnit测试时灵时不灵的情况,简直是多线程调试的噩梦!我之前碰过好几个类似的问题,几乎都是线程同步相关的坑,给你梳理几个最可能的排查方向:

1. 先查同步机制的漏洞——90%的概率出在这

如果你的计数器是多线程共享的资源,没做好同步的话,竞态条件、虚假唤醒、通知遗漏都会导致线程挂死:

  • 要是你用了wait()/notify(),一定要记住三个铁则:
    1. 必须在synchronized块里调用(持有对象锁);
    2. 永远用while循环判断等待条件,别用if——防止虚假唤醒后直接跳过条件检查;
    3. 用notifyAll()代替notify(),避免只唤醒了一个不相关的线程,其他线程永远睡过去。
  • 要是计数器是普通的int,赶紧换成AtomicInteger这种原子类,或者给读写操作加synchronized锁,不然多线程同时读写会把值搞乱,后续线程可能因为等不到预期的计数器值一直挂着。

举个典型的错误写法:

// 错:用if判断,没在循环里
if (counter < printTimes) {
    lock.wait(); // 虚假唤醒后直接执行,逻辑全乱
}

正确打开方式:

synchronized(lock) {
    // 循环判断,确保唤醒后条件真的满足
    while (counter < printTimes) {
        lock.wait();
    }
    counter++;
    lock.notifyAll(); // 唤醒所有等待的线程
}

2. 排查死锁可能性

如果你的代码里用到了多把锁,线程获取锁的顺序不一致就会触发死锁——俩线程互相攥着对方要的锁,谁也动不了。排查方法:

  • 运行时用jstack命令导出线程栈,看看有没有线程处于BLOCKED状态,且等待的锁被其他线程占着;
  • 检查所有获取多锁的代码,强制所有线程按相同的顺序拿锁,比如必须先拿锁A再拿锁B,不能有的线程先拿B再拿A。

3. JUnit测试的特殊坑

JUnit测试的主线程跑完就会结束,不管你的工作线程有没有做完,所以有时候看起来是测试失败,其实是线程还没执行完。解决办法:

  • 用CountDownLatch或者Thread.join()让测试主线程等所有工作线程跑完再结束;
  • 测试类里的静态资源(比如共享锁、计数器)一定要在每个测试方法前重置,不然上一次测试的状态会影响下一次;
  • 别用ExecutorService那种线程池,除非你在测试结束后手动shutdown()并awaitTermination(),不然线程池里的线程会残留,导致后续测试混乱。

4. 加日志!加日志!加日志!

既然没有报错,那就把关键节点的日志打满——线程ID、计数器值、锁状态、等待/唤醒的时机都打印出来。比如:

synchronized(lock) {
    System.out.printf("线程[%d]:当前计数器=%d,进入等待%n", Thread.currentThread().getId(), counter);
    while (counter < printTimes) {
        lock.wait();
        System.out.printf("线程[%d]:被唤醒,当前计数器=%d%n", Thread.currentThread().getId(), counter);
    }
    counter++;
    System.out.printf("线程[%d]:计数器递增至%d,通知所有线程%n", Thread.currentThread().getId(), counter);
    lock.notifyAll();
}

看第二次运行时日志停在哪一行,就能精准定位是卡在等待锁,还是等不到计数器值了。

如果能贴出你的核心代码片段,比如线程逻辑、计数器同步的部分,我能帮你更快找到问题~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:07:31