如何安全地使用 Lock 的unlock() 而不会抛出异常导致程序终止
背景
我们使用 synchronized 的话,不需要我们手工操作lock() 和 unlock(),但是在使用 juc Lock 的时候就需要注意,不注意这些细节,将为你的程序埋下坑,卖容易发现的坑还好,偏偏有些坑是比较隐蔽的。
正确的 unlock()
如何正确的使用Lock#unlock(),如下
-
建议别用
Lock lock = new ReentrantLock()
这样的代码,直接用实现类ReentrantLock lock = new ReentrantLock()
-
建议直接用 try-catch 包住异常,并且不处理任何异常,不打印日志
public safeUnlock(Lock lock) { try { lock.unlock(); } catch(Exception e) { } }
-
有 isHeldByCurrentThread的,也可以用
if (lock.isHeldByCurrentThread()) { lock.unlock(); }
但是并不是什么 Lock 的子类都有
isHeldByCurrentThread()
,所以还是统一用try-catch,一了百了,又统一代码风格。- 比如 ReadLock就没有该方法
示例
1、例子1:ReentrantLock 的关闭
unsafeUnlock的写法是危险的,假如在lock.lock() 的上面写了代码,并且这些代码发生了异常,惨了,没获得锁就进行解锁,会抛出了异常。正确的方式是先判断 isHeldByCurrentThread()
private static void testUnlockReentrantLock() {
ReentrantLock lock = new ReentrantLock();
try {
int i = 8 / 0;
lock.lock();
// do sth
} finally {
unsafeUnlock(lock);// 有风险
// safeUnlock(lock);// 正确方法
}
}
private static void unsafeUnlock(ReentrantLock lock) {
lock.unlock();
}
private static void safeUnlock(ReentrantLock lock) {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
// 注意: 更安全是先判断 lock != null.不过这个不是今天的主角,就不突出NPE判断了
}
2、例子2:ReentrantLock的tryLock的关闭
可以看到,第二个线程因为tryLock设置了超时时间,实际是不能获得线程的