我们使用 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就没有该方法
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设置了超时时间,实际是不能获得线程的,如果直接lock.unlock() 会抛出异常。正确的解法有两种
- unlock之间先判断 isHeldByCurrentThread() (推荐)
- 使用布尔类型的值接收tryLock的返回值,如果是true才进行unlock
private static void testUnlockTryLock() {
ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
try {
lock.lock();
sleep(5);
} finally {
safeUnlock(lock);
}
}).start();
sleep(1);
new Thread(() -> {
try {
int i = 8 / 0;
lock.tryLock(2, TimeUnit.SECONDS);
// do sth
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unsafeUnlock(lock);// 有风险
// safeUnlock(lock);// 解决办法
}
}).start();
}
3、例子3:关于WriteLock的关闭
同样的道理,writeLock的关闭是一定要有 isHeldByCurrentThread() 判断的
private static void testUnlockReadWriteLock() {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
new Thread(() -> {
try {
readLock.lock();
sleep(5);
} finally {
readLock.unlock();
}
}).start();
sleep(1);
new Thread(() -> {
try {
int i = 8 / 0;
writeLock.lock();
// do sth
} finally {
writeLock.unlock();// 有风险
// if (writeLock.isHeldByCurrentThread()) writeLock.unlock(); // 正确
}
}).start();
}
同样道理,writeLock如果使用tryLock,也是要用
private static void testUnlockReadWriteLock() {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
new Thread(() -> {
try {
readLock.lock();
sleep(5);
} finally {
readLock.unlock();
}
}).start();
sleep(1);
new Thread(() -> {
try {
writeLock.tryLock(2, TimeUnit.SECONDS);
// do sth
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();// 有风险
// if (writeLock.isHeldByCurrentThread()) writeLock.unlock();// 正确
}
}).start();
}
4、例子4:ReadLock 的关闭
这个似乎没有什么好办法解决,因为ReadLock根本就没有 isHeldByCurrentThread() 方法。只能用 try-catch 包住unlock() 方法
private static void testUnlockReadLock() {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
new Thread(() -> {
try {
int i = 8 / 0;
readLock.lock();
sleep(5);
} finally {
readLock.unlock();// 错误示范
//try { readLock.unlock();} catch (Exception e) {}// 正确示范
}
}).start();
}
5、例子5:ReadLock 的 tryLock 的关闭
下面的例子是WriteLock占用5秒,readLock尝试2秒内获得锁,否则不再等待。此时进行unlock会报错,由于ReadLock没有 isHeldByCurrentThread(),所以也只能用try-catch判断了
private static void testUnlockReadLockTryLock() {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
new Thread(() -> {
try {
writeLock.lock();
sleep(5);
} finally {
if (writeLock.isHeldByCurrentThread()) writeLock.unlock();
}
}).start();
sleep(1);
new Thread(() -> {
try {
readLock.tryLock(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();// 错误示范
// try { readLock.unlock();} catch (Exception e) {}// 正确示范
}
}).start();
}