本文正在参加「Java主题月 – Java Debug笔记活动」,详情查看 活动链接
[已解决] 生产环境线程死锁造成的服务器无响应错误-线上问题定位与解决方案
概述
最近团队有人遇到线程死锁的情况,在这里介绍一下情况,以及如何解决的
问题
首先讲讲是怎么知道出问题了,线上这个应用是4台机器负载,有用户反馈有的页面进入超卡,或者就是进不去。
定位
其实当时也是挺懵逼的,测试这边用账号进入,挺流畅的啊。后面根据几个用户提供的链路,发现页面卡的时候,链路请求的机器都是第1台机器的ip。
那么问题就简单很多了。
首先,通过链路直接去看机器上的日志,某几个方法请求的时候,基本都是超时。
通过error日志查看不出更多的问题。
那么看看jstack Dump 日志文件中的线程状态,下载机器的jstack日志进行一个分析。
在这里分享一个分析网站:
spotify.github.io/threaddump-…
可以在线将jstack的日志更加直观的展示出来。
分析后看到的日志很简单:
//... 省略
"car_lib_sync86": awaiting notification on [0x00000007239a07c0]
"car_lib_sync87": awaiting notification on [0x00000007239a07c0]
"car_lib_sync88": awaiting notification on [0x00000007239a07c0]
"car_lib_sync89": awaiting notification on [0x00000007239a07c0]
"car_lib_sync9": awaiting notification on [0x00000007239a07c0]
"car_lib_sync90": awaiting notification on [0x00000007239a07c0]
"car_lib_sync91": awaiting notification on [0x00000007239a07c0]
"car_lib_sync92": awaiting notification on [0x00000007239a07c0]
"car_lib_sync93": awaiting notification on [0x00000007239a07c0]
"car_lib_sync94": awaiting notification on [0x00000007239a07c0]
"car_lib_sync95": awaiting notification on [0x00000007239a07c0]
"car_lib_sync96": awaiting notification on [0x00000007239a07c0]
"car_lib_sync97": awaiting notification on [0x00000007239a07c0]
"car_lib_sync98": awaiting notification on [0x00000007239a07c0]
"car_lib_sync99": awaiting notification on [0x00000007239a07c0]
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
198 threads with this stack:
"DubboServerHandler-172.16.35.143:20880-thread-1": awaiting notification on [0x0000000725ee76f0]
"DubboServerHandler-172.16.35.143:20880-thread-10": awaiting notification on [0x0000000725ee76f0]
"DubboServerHandler-172.16.35.143:20880-thread-100": awaiting notification on [0x0000000725ee76f0]
"DubboServerHandler-172.16.35.143:20880-thread-101": awaiting notification on [0x0000000725ee76f0]
//... 省略
复制代码
在这里可以看到car_lib_sync*的线程都是在等待某一个线程,很直白
线程死锁了。在这里不得不说一声,开线程池记得给线程起一个前缀名称。
在这里,我直接在项目中搜索“ar_lib_sync”
看到如下的代码:
@Slf4j
@Service
public class CarThreadPool extends ThreadPoolExecutor {
public CarThreadPool(){
super(300, 500, 1000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1000), new ThreadFactoryImpl("car_lib_sync"), new ThreadPoolExecutor.CallerRunsPolicy());
}
}
复制代码
然后看调用方,好家伙,我直接好家伙。先不说其他,300初始化的线程已经有点离谱了。
根本原因
A类中调用B类
@Autowired
private CarThreadPool carThreadPool;
Future<Boolean> validateTask = threadPool.submit(() -> {
//...
b.a();
});
Boolean isok = validateTask.get(10, TimeUnit.SECONDS);
复制代码
B类中a方法:
@Autowired
private CarThreadPool carThreadPool;
threadPool.submit(() -> {
//...
});
复制代码
死锁原因逐渐明朗。
根本原因在于:A类调用行为属于父任务,同时它又包含了多次子任务B.a,而父任务和子任务使用的是同一个业务线程池。当线程池中全部都是执行中的父任务时,并且所有父任务都存在子任务未执行完,这样就会发生死锁。
在这里用个图简单的表示一下。
假设核心的线程数为2个,此时正在执行A1,A2。另外,B.a1和B.a3执行完成了。而B.a2和B.a3在队列中等待调度,但是线程数不够了。而A1和A2父任务由于子任务没有执行完成,也不可能执行完成。所以就发生了死锁。
这在线上出现时是一个概率问题,不一定一定会出现。
但是知道了问题,就好解决。
解决
解决方法很粗暴,由于上线紧急发布,修复方式简单。所有的类,每个类都拥有一个单独的线程池。此种方式可以隔离父子任务。
而且,每个线程池中线程的数量调整为10.
private static final ExecutorService CAR_THREAD_POOl = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy());
复制代码
且使用无界队列。
在这里不进行探讨其他的更优解决方案,此种方案,完美的解决线上死锁问题
大家有更好的方法,欢迎评论区讨论。
总结
这种情况的出现,完全是由于开发者对于线程池的不理解造成的。
-
线程数不需要设置过大,合适的才是最好的,建议CPU核*2 + 1
-
存在父子调用关系的,一定不要使用同一线程池