🥼/背景
因为锁是非常重要且占用资源的,所以基本上都知道需要解锁
常见的代码示例如下
Lock lock = new ReentrantLock();
try {
lock.lock();
//加锁后的操作xxx
} catch (Exception e) {
//异常处理
} finally {
//finally里面解锁,防止异常导致未释放锁
lock.unlock();
}
这个其实是没啥问题,(因为lock.lock()
是阻塞的,必定成功),但是如果用tryLock获取锁是否也是这样呢?(因为tryLock
是尝试加锁,可能未拿到锁)
如果没有获取到锁需要解锁么?如果未获取到锁去解锁是不影响还是异常呢?正确的写法是怎样呢?
🎡/试验
其实实验的方法很简单,写个测试类实验一下就可以了
1、如果无锁去解锁是不影响还是异常呢?
@Test
public void lockTest() {
Lock lock = new ReentrantLock();
lock.unlock();
}
结果:
会产生异常报错
2、tryLock没有获取到锁需要解锁么?(其实从上面的尝试已经知道无锁的情况我们不应该再去解锁)
我们模拟一下,因为需要让它获取锁失败,所以我们利用多线程模拟下并发情况下,获取失败的效果。
代码如下,看起来没啥问题 (实际上有问题 -,-)
@Test
public void lockTest() {
Lock lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
try {
if (lock.tryLock()) {
System.out.println("hello" + finalI);
}
} finally {
lock.unlock();
}
}).start();
}
boolean sleep = ThreadUtil.sleep(3000);
System.out.println("over" + sleep);
}
结果
部分未获取到锁的去解锁,会产生异常报错
那么我们就明白了,无锁或未获取到锁,我们是不需要去解锁的,去解锁反而还会产生异常。
🎪/理解归纳
一般正确的写法(-,-):
@Test
public void lockTest() {
Lock lock = new ReentrantLock();
boolean locked = false;
try {
if (!lock.tryLock()) {
return;
}
//业务处理
System.out.println("hello word!");
} finally {
if (locked) {
lock.unlock();
}
}
}
如果不是void 的方法就需要返回对象或者null
public String getSomeData(String req) {
Lock lock = new ReentrantLock();
boolean locked = false;
try {
if (!lock.tryLock()) {
return null;
}
//业务处理
System.out.println("hello word!" + req);
return "some data result";
} finally {
if (locked) {
lock.unlock();
}
}
}
实际的代码情况会更加复杂,比如加锁失败不想返回任何值,想抛出指定类型的异常 (触发事务回滚),统一处理加锁失败的问题。
在web开发中,可以使用Spring的
全局异常拦截
之后,通过异常类型
匹配,直接返回自定义数据给前端(可能是标准的弹窗提
示,如:系统繁忙,请稍后重试 ),那么不必再方法里面返回Ret
这样的类型了,降低方法的耦合性。(推荐!!!)
如果是上面的情况,我们需要抛出指定异常,那么代码写出来可能就是这样了。
@Transactional(rollbackFor = Exception.class)
public String getSomeData(String req) {
Lock lock = new ReentrantLock();
boolean locked = false;
try {
locked = lock.tryLock(2, TimeUnit.SECONDS);
if (!locked) {
throw new BizException(Const.ResultCode.FAIL, "请稍后重试");
}
//业务处理 一般有事务操作
System.out.println("hello word!" + req);
return "some data result";
} catch (BizExceptione e) {
log.error(e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new BizException(Const.ResultCode.FAIL, "系统开小差了.");
} finally {
if (locked) {
lock.unlock();
}
}
}
那么一堆的临时变量和判断,不必要的逻辑耦合,代码混乱,非常影响效率
finally {
if (locked) {
lock.unlock();
}
}
看着非常难受(强迫症 Q,Q)
🎏/解决方案
其实解决方案也非常简单,就是把加锁的东西提出来就好了,不要和业务混合在一起
这样不仅不需要临时变量的判断(取名字是个痛苦的事情,-。-),而且不需要对异常额外的处理哟。
推荐优雅且正确的代码如下:(分布式锁等处理逻辑类似)
@Transactional(rollbackFor = Exception.class)
public String getSomeData(String req) {
Lock lock = new ReentrantLock();
if (!lock.tryLock()) {
throw new BizException(Const.ResultCode.FAIL, "请稍后重试");
}
try {
//业务处理 一般有事务操作
System.out.println("hello word!" + req);
return "some data result";
}catch (Exception e) {
log.error(e.getMessage(), e);
throw new BizException(Const.ResultCode.DEFAULT_FAIL);
} finally {
lock.unlock();
}
}
先进行了锁的检查,那么就可以确保,后面的lock是存在的,可以放心的unlock.
🧵/结论
1、Lock 的unlock()方法必须要加锁成功才能执行,不然会抛异常
2、不是把unlock()方法写在finally里面就一定正确,tryLock()如果加锁失败,在finally里面会再抛异常
3、把加锁操作和业务分开,能避免大量判断和无用的耦合逻辑,增加代码优雅可读性
(偷图,侵删)