尴尬的死锁

798 阅读2分钟

背景

昨天早上来公司就被喊着看一个bug,这个bug的内容就是点击一个按钮,服务一直没有响应,前端的页面效果在那一直转圈圈。

排查问题

  1. 去看是什么场景发生的这个bug.
  2. 去看相关接口.
  3. 根据相关接口去看kibana日志.

在排查日志的时候发现有一个日志很奇怪……一直报:为了获取锁,已经等待了xx秒

按这个秒数换算下来已经是等了大半天了。第一反应就是死锁了,不然不至于等那么久。

  1. 根据这行日志的文字去代码里定位具体代码。(简要代码如下
private final AtomicBoolean inProcessing = new AtomicBoolean(false);

protected void wait4WorkLock() {
    boolean canWork;
    do {
        canWork = getLock();
        if (!canWork) {
            sleepForMillisecond(500);
        }
    } while (!canWork);
}

private boolean getLock() {
    return inProcessing.compareAndSet(false, true);
}

这三块的代码挺容易理解的,定义一个原子布尔类型当作锁,初始是false,如果成功将false变成true就表示获得到了锁。

  1. 由于用的是cas做锁的,那么猜测死锁的可能性是:嵌套地去获取cas锁了

伪代码:

public void function() {
    // 获得锁,做A事情
    wait4WorkLock();
    if(a == b) {
        functionB();
    }
    unLock();
}

public void functionB() {
    // 获得锁,做B事情
    // 在这个例子里这儿会造成死锁,死等待,因为在function方法里布尔原子已经是true了,不能在true的情况下改成true
    wait4WorkLock();
    functionB();
    unLock();
}
  1. 带着这个去代码里求证,果然……

image.png

问题的根本原因

原子布尔类型不支持重入,不是可重入锁,对于同一个线程不能多次获取。

解决办法

确认了问题的根本原因,那么解决办法就有很多了。

  1. 使用synchronized关键字。
  2. 使用reentrantlock。

总结

  1. 线上的死锁问题分析
  2. 将cas锁换成可重入锁。