Java ReentrantLock重入锁详解
重入锁(ReentrantLock)是对 synchronized 的升级,synchronized 是通过 JVM 实现的,ReentrantLock 是通过 JDK 实现的。
重入锁指的是可以给同一个资源添加多个锁,并且解锁的方式与 synchronized 也有不同。synchronized 的锁是线程执行完毕之后会自动释放的,ReentrantLock 的锁必须手动释放,可以通过 ReentrantLock 实现访问量统计。
举个简单的例子:
我们说过 ReentrantLock 需要手动解锁,如果我们只加锁而不手动解锁:
图 1 程序发生阻塞
可以看到这里只输出了第 1 位访客信息,然后程序就静止了,一直处于运行状态但是不会自动结束。原因就是线程 1 进来,加了两把锁。执行完线程 1 的输出语句之后,只释放了一把锁。线程 2 永远无法获得第 2 把锁,就一直处于阻塞状态,程序也就一直卡在这里了,所以使用重入锁的时候需要注意加了几把锁就必须释放几把锁。
ReentrantLock 除了可重入之外,还有一个可中断的特点,可中断是指某个线程在等待获取锁的过程中可主动终止线程,通过调用对象的 lockInterruptibly() 来实现。
假设这样一个场景,线程 1 和线程 2 并行访问某个方法,该方法执行完成需要 5000ms。肯定会有一个线程先拿到锁,然后另外一个线程就进入阻塞状态。现在设置当线程启动 1000ms之后,没有拿到锁的线程自动中断,具体实现如下:
图 2 程序自动中断
ReentrantLock 还具备限时性的特点,指可以判断某个线程在一定的时间内能否获取锁,通过调用 tryLock(long timeout, TimeUnit unit) 方法来实现。其中 timeout 指时间数值,unit 指时间单位,返回值为 boolean 类型,true 表示在该时间段内获取了锁,false 表示在该时间段内没有获取锁,具体实现如下:
很显然 5000ms 大于 3s,会有一个线程是肯定拿不到锁的,运行程序,结果为:
重入锁指的是可以给同一个资源添加多个锁,并且解锁的方式与 synchronized 也有不同。synchronized 的锁是线程执行完毕之后会自动释放的,ReentrantLock 的锁必须手动释放,可以通过 ReentrantLock 实现访问量统计。
举个简单的例子:
public class Account implements Runnable{ private static int num; private ReentrantLock reentrantLock = new ReentrantLock(); @Override public void run() { // TODO Auto-generated method stub reentrantLock.lock(); num++; System.out.println(Thread.currentThread().getName()+"是当前的第"+num+"位访客"); reentrantLock.unlock(); } }首先需要实例化 ReentrantLock 的成员变量,在业务方法中需要加锁的地方直接调用对象的 lock() 方法即可,同理需要解锁的地方直接调用对象的 unlock() 方法即可,运行程序,结果是:
线程1是当前的第1位访客
线程2是当前的第2位访客
public class Account implements Runnable{ private static int num; private ReentrantLock reentrantLock = new ReentrantLock(); @Override public void run() { // TODO Auto-generated method stub reentrantLock.lock(); reentrantLock.lock(); num++; System.out.println(Thread.currentThread().getName()+"是当前的第"+num+"位访客"); reentrantLock.unlock(); reentrantLock.unlock(); } }
我们说过 ReentrantLock 需要手动解锁,如果我们只加锁而不手动解锁:
public class Account implements Runnable{ private static int num; private ReentrantLock reentrantLock = new ReentrantLock(); @Override public void run() { // TODO Auto-generated method stub reentrantLock.lock(); reentrantLock.lock(); num++; System.out.println(Thread.currentThread().getName()+"是当前的第"+num+"位访客"); reentrantLock.unlock(); } }运行程序,结果如下图所示:
图 1 程序发生阻塞
可以看到这里只输出了第 1 位访客信息,然后程序就静止了,一直处于运行状态但是不会自动结束。原因就是线程 1 进来,加了两把锁。执行完线程 1 的输出语句之后,只释放了一把锁。线程 2 永远无法获得第 2 把锁,就一直处于阻塞状态,程序也就一直卡在这里了,所以使用重入锁的时候需要注意加了几把锁就必须释放几把锁。
ReentrantLock 除了可重入之外,还有一个可中断的特点,可中断是指某个线程在等待获取锁的过程中可主动终止线程,通过调用对象的 lockInterruptibly() 来实现。
假设这样一个场景,线程 1 和线程 2 并行访问某个方法,该方法执行完成需要 5000ms。肯定会有一个线程先拿到锁,然后另外一个线程就进入阻塞状态。现在设置当线程启动 1000ms之后,没有拿到锁的线程自动中断,具体实现如下:
public class StopLock implements Runnable { public ReentrantLock reentrantLock = new ReentrantLock(); @Override public void run() { // TODO Auto-generated method stub try { reentrantLock.lockInterruptibly(); System.out.println(Thread.currentThread().getName()+" get lock"); Thread.currentThread().sleep(5000); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } finally { reentrantLock.unlock(); } } } public class Test { public static void main(String[] args) { StopLock lock = new StopLock(); Thread t1 = new Thread(lock, "线程1"); t1.start(); Thread t2 = new Thread(lock, "线程2"); t2.start(); try { Thread.currentThread().sleep(1000); t2.interrupt(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }运行程序,结果如下图所示:
图 2 程序自动中断
ReentrantLock 还具备限时性的特点,指可以判断某个线程在一定的时间内能否获取锁,通过调用 tryLock(long timeout, TimeUnit unit) 方法来实现。其中 timeout 指时间数值,unit 指时间单位,返回值为 boolean 类型,true 表示在该时间段内获取了锁,false 表示在该时间段内没有获取锁,具体实现如下:
public class TimeLock implements Runnable{ public ReentrantLock reentrantLock = new ReentrantLock(); @Override public void run() { // TODO Auto-generated method stub try { if(reentrantLock.tryLock(3, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName()+"get lock"); Thread.currentThread().sleep(5000); }else { System.out.println(Thread.currentThread().getName()+"not lock"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(reentrantLock.isHeldByCurrentThread()) { reentrantLock.unlock(); } } } } public class Test { public static void main(String[] args) { TimeLock lock = new TimeLock(); new Thread(lock,"线程1").start(); new Thread(lock,"线程2").start(); } }线程 1 和线程 2 并行访问,业务方法的执行需要 5000ms。reentrantLock.tryLock(3, TimeUnit.SECONDS) 表示如果线程启动之后的 3s 内该线程没有拿到锁,则返回 false,反之返回 true。
很显然 5000ms 大于 3s,会有一个线程是肯定拿不到锁的,运行程序,结果为:
线程1get lock
线程2not lock
public class TimeLock implements Runnable{ public ReentrantLock reentrantLock = new ReentrantLock(); @Override public void run() { // TODO Auto-generated method stub try { if(reentrantLock.tryLock(6, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName()+"get lock"); Thread.currentThread().sleep(5000); }else { System.out.println(Thread.currentThread().getName()+"not lock"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(reentrantLock.isHeldByCurrentThread()) { reentrantLock.unlock(); } } } }reentrantLock.tryLock(6, TimeUnit.SECONDS) 表示如果线程启动之后的 6s 内该线程没有拿到锁,则返回 false,反之返回 true,运行程序,结果为:
线程1get lock
线程2get lock