如何安全地使用 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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值