多线程应用JUnit测试后及重复运行时无法运行故障求助
排查多线程打印机计数器的间歇性失效问题
这种偶尔正常、偶尔线程永久休眠、JUnit测试时灵时不灵的情况,简直是多线程调试的噩梦!我之前碰过好几个类似的问题,几乎都是线程同步相关的坑,给你梳理几个最可能的排查方向:
1. 先查同步机制的漏洞——90%的概率出在这
如果你的计数器是多线程共享的资源,没做好同步的话,竞态条件、虚假唤醒、通知遗漏都会导致线程挂死:
- 要是你用了
wait()/notify(),一定要记住三个铁则:- 必须在
synchronized块里调用(持有对象锁); - 永远用
while循环判断等待条件,别用if——防止虚假唤醒后直接跳过条件检查; - 用
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
相关产品推荐
相关产品推荐

