ReentrantLock正确使用指南:避免坑点的最佳实践
ReentrantLock正确使用指南:避免坑点的最佳实践
在Java并发编程中,ReentrantLock是一种常用的可重入锁。但是,如何正确使用ReentrantLock却是一个容易被忽视的问题。本文将通过具体的代码示例和异常分析,详细讨论ReentrantLock的两种写法及其优劣。
ReentrantLock
ReentrantLock是一种可重入锁,它提供了比synchronized更灵活的锁获取和释放方式。但是,如果不正确地使用ReentrantLock,可能会导致一些意想不到的问题。
写法一(Oracle官方推荐)
ReentrantLock lock = new ReentrantLock();
public void calculate(){
lock.lock();
try{
}finally{
lock.unlock();
}
}
写法二(大厂不推荐)
ReentrantLock lock = new ReentrantLock();
public void calculate(){
try{
lock.lock();
}finally{
lock.unlock();
}
}
第二种写法的缺点
将lock.lock()
方法写在try里面会导致真实的崩溃信息被覆盖。假设我们在lock.lock();
上方加入这样一行代码int num = 1/0;
,代码如下:
ReentrantLock lock = new ReentrantLock();
public void calculate(){
try{
int num = 1/0;
lock.lock();
}finally{
lock.unlock();
}
}
此时正常我们所期望能出现的崩溃信息是java.lang.ArithmeticException: / by zero
,但是实际出现的异常则是java.lang.IllegalMonitorStateException
。
原因是因为上述代码无论是否会抛出异常,finally中的代码块都会被执行,最后会调用unlock()
方法。而unlock()
方法最终会调用ReentrantLock$Sync.tryRelease()
方法,然后会判断当前线程是否是拥有锁的线程,如果不是则会抛出异常,从而会将真实的崩溃信息覆盖掉。
将lock.lock()
方法写在try里面的第一行
这时有人则会说。那我将代码int num = 1/0
放在lock.lock()
方法的下面不就好了;网上也有很多这样的说法(也就是将lock.lock()
方法写在try代码块的第一行,保证lock()
方法前面没有任何代码)。这样做对吗?难道不会出现问题嘛?答案是不一定。只能说出现问题的概率很低。
ReentrantLock lock = new ReentrantLock();
public void calculate(){
try{
lock.lock();
int num = 1/0;
}finally{
lock.unlock();
}
}
lock()
方法的源码是这样说的:lock()
方法可能会抛出异常,然后会执行finally代码块中的unlock()
方法,在unlock()
方法中,会检查当前线程是否是拥有锁的线程,如果不是则会抛出异常,同样会导致真实的崩溃信息丢失(导致覆盖掉lock.lock()
出现异常的信息)。
这里有人又会问:那lock.lock()
方法写在try外面和写在try中有什么区别嘛?
写在外面的话,lock()
方法抛出异常的话就不会执行后续代码了。
为什么要将unlock()
方法写在finally代码块中
因为如果程序出现异常,依然能够保证锁会被释放掉,避免死锁的发生。这里需要注意一点:unlock()
方法需要放到finally代码块的第一行,避免因为其它代码出现异常,导致unlock()
方法无法执行,造成死锁。
将lock.lock()
方法写在try方法外面
思考一下:写在外卖你一定不会有问题吗?答案:不一定。假设我们这样写:
ReentrantLock lock = new ReentrantLock();
public void calculate(){
lock.lock();
int num = 1/0;
try{
}finally{
lock.unlock();
}
}
此时我们虽然将lock.lock()
方法写在了try方法的外面,但是紧接着下一行代码出现了异常;并且此时已经加锁了同时没有进入try方法。这时会导致锁无法释放。导致死锁。导致其它线程无法获取到锁。
如何避免上面问题的出现
在使用可重入锁的时候,需要注意以下几点:
lock()
方法必须写在try代码块外面lock()
方法和try代码块之间,不能有其它的代码,避免出现异常,导致锁无法释放,造成其它线程无法获取到锁unlock()
方法要放到finally代码块的第一行,避免因为其它代码块出现异常导致unlock()
方法无法执行,锁无法释放。