如何安全地使用 Lock 的unlock() 而不会抛出异常导致程序终止

如何安全地使用 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设置了超时时间,实际是不能获得线程的,如果直接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();
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用中提到了在使用ReentrantLock时,如果在lock.lock()之前发生了异常,那么在解锁时就异常。为了避免这种情况发生,需要先判断锁是否被当前线程持有,再进行解锁操作。所以正确的解法是先调用lock.isHeldByCurrentThread()方法来判断锁是否被当前线程持有,再进行解锁操作。另外,还可以在try-catch语句中使用布尔类型的值来接收lock.tryLock()的返回值,如果返回值为true,则说明锁已经被当前线程获得,可以进行解锁操作。 在例子2中,使用了ReentrantLock的tryLock()方法,如果获取锁的过程中发生了异常,就需要在finally块中进行解锁操作。同样,为了避免解锁时异常,可以先判断锁是否被当前线程持有,再进行解锁操作。所以正确的解法是先调用lock.isHeldByCurrentThread()方法来判断锁是否被当前线程持有,再进行解锁操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [如何安全地使用 Lockunlock() 而不异常导致程序终止](https://blog.csdn.net/w8y56f/article/details/115608666)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatgptT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值