[Java] Lock(锁)的tryLock失败是否需要unlock?

🥼/背景

因为锁是非常重要且占用资源的,所以基本上都知道需要解锁
常见的代码示例如下

        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、把加锁操作和业务分开,能避免大量判断和无用的耦合逻辑,增加代码优雅可读性
在这里插入图片描述
(偷图,侵删)

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Java 中的 tryLock 方法可以用来实现自旋tryLock 方法尝试获取,如果获取成功,则立即返回 true,否则返回 false。可以在循环中调用 tryLock,直到成功获取为止。这样就可以实现自旋的效果。 例如: ``` Lock lock = new ReentrantLock(); while(!lock.tryLock()){ //do something } try{ //critical section }finally{ lock.unlock(); } ``` 这样做的缺点是会占用CPU资源,如果竞争激烈的话会导致性能问题。 ### 回答2: Java中的tryLock()方法是java.util.concurrent.locks.ReentrantLock类中的一个方法,用于实现自旋。自旋是一种基于循环的,当线程尝试获取时,如果发现已被其他线程持有,则不会进入等待状态,而是通过循环不断尝试获取,直到获取成功为止。 tryLock()方法可以尝试获取,如果当前没有被其他线程持有,则获取成功并返回true;如果已被其他线程持有,则获取失败,并立即返回false,不会阻塞线程。使用该方法可以避免线程进入等待状态,减少线程切换的开销,提高程序的执行效率。 tryLock()方法还提供了重载方法,可以设置超时时间,在限定的时间内尝试获取。如果超过指定的时间仍未获取到,则放弃获取,返回false。通过设置超时时间,可以防止线程长时间等待,避免可能的死情况发生。 自旋在某些场景下可以提高程序的性能,特别是对于的竞争不激烈、持有的时间较短的情况。但是在一些高并发场景下,长时间的自旋可能会消耗大量的CPU资源,导致程序性能下降。因此,需要根据具体的业务场景来选择合适的机制。 综上所述,JavatryLock()方法实现了自旋,通过不断尝试获取而不进入等待状态,提高了程序的执行效率。但是需要注意在高并发场景下的使用,避免长时间的自旋带来的性能问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值