ReentrantLock 用法详解

2018-08-06 15:48:31   最后更新: 2018-08-06 15:51:18   访问数量:242




此前的日志中我们介绍了 synchronized 锁的使用及实现原理:

synchronized 的使用及实现原理

文中,我们看到,jdk1.6 对 synchronized 锁进行了一系列的优化,使得我们再也不用为 synchronized 锁的性能担忧,在此之前,synchronized 锁因为其性能问题是很少被使用的,那时,最常用的锁结构就是今天我们要介绍的 ReentrantLock 锁

虽然时至今日,优化后的 synchronized 锁的性能已经与 ReentrantLock 接近,但 ReentrantLock 仍然具备着 synchronized 所不具备的很多优势:

  1. 吞吐量更高、执行效率更高
  2. 支持公平锁、非公平锁两种模式
  3. 更加灵活精准的等待与唤醒
  4. 可中断锁、轮询锁
  5. 可伸缩性强
  6. 支持条件变量

因此,在高度竞争的并发环境下,以及较为复杂的使用场景中,ReentrantLock 都是 synchronized 的有力替代品

 

本篇日志中,我们就来介绍一下 ReentrantLock 的用法,下一篇日志中,我们详细的剖开源码解析 ReentrantLock 的实现原理

 

 

 

ReentrantLock 类提供了最基本的加锁和解锁方法:

public void lock(); public void unlock();

 

 

我们通过最基本的加锁方法实现上一篇日志中提到的自增方法锁

class Counter { private static int counter = 0; private static ReentrantLock lock = new ReentrantLock(); public static int getCounter() { return counter; } public static void increase() { try { lock.lock(); counter++; } finally { lock.unlock(); } } private Counter() {}; }

 

这个方法保证了线程安全,他和 synchronized 关键字实现了相同的效果:

class Counter { private static int counter = 0; public static int getCounter() { return counter; } public synchronized static void increase() { counter++; } private Counter() {}; }

 

显然,synchronized 关键字的实现更为简洁和清晰,同时,如果 ReentrantLock 忘记调用 unlock 方法将会造成死锁,这是必须要注意的一点

因此,如果仅仅是想要进行上面代码中这样的加锁和解锁,synchronized 还是最好的选择

 

使用 synchronized 锁是不保证等待的线程获取到锁的顺序的,这就是非公平锁,除了默认的非公平锁构造方法外,ReentrantLock 还提供了一个带有 boolean 参数的构造方法:

public ReentrantLock(boolean fair);

 

 

如果传入参数为 true,则会创建公平锁,所谓的公平锁,就是保证了先进入等待的线程一定先获取到锁

 

可以通过 isFair 方法查询 ReentrantLock 对象是否是公平锁:

public final boolean isFair();

 

 

ReentrantLock 提供了 tryLock 方法与 lockInterruptibly 方法用来实现非阻塞式锁、时间限制锁与可中断锁

public boolean tryLock() // 尝试获取锁,立即返回获取结果 public boolean tryLock(long timeout, TimeUnit unit) // 尝试获取锁,最多等待 timeout 时长 public void lockInterruptibly() // 可中断锁,调用线程 interrupt 方法,则锁方法抛出 InterruptedException

 

以上三种锁方式,synchronized 都是无法实现的,正如我们上一篇日志中所提到,interrupt 方法是不会中止正在等待获取 synchronized 锁的线程的

 

Object 类提供了只能在 synchronized 代码块中使用的 wait、notify、notifyAll,ReentrantLock 也拥有类似但更为强大的等待和唤醒机制,这就是通过 Condition 对象唤醒的

 

Condition

通过 newCondition 方法,可以创建出 Condition 对象

public Condition newCondition();

 

 

Condition 接口提供了如下的方法:

void await(); // 可被中断的等待 boolean await(long time, TimeUnit unit); // 最多等待 time 时长的可中断等待 long awaitNanos(long nanosTimeout); // 最多等待 nanosTimeout 毫秒的可中断等待 boolean awaitUntil(Date deadline); // 等待直到指定时间的可中断等待 void awaitUninterruptibly(); // 不可中断的等待 void signal(); // 唤醒一个线程 void signalAll(); // 唤醒所有等待中的线程

 

 

上面的五个等待方法中,除了 awaitUninterruptibly 方法,其他四个都可以被 interrupt 方法中断,而 signal 和 signalAll 方法可以中断上述所有等待方法

但是,signal 和 signalAll 方法只能唤醒通过当前 Condition 对象调用过等待方法的线程

基于上述特性,我们可以精准的控制让某个指定的线程被唤醒,而 Object 的 notify、notifyAll 方法的唤醒则是随机的,同一个 ReentrantLock 每次调用 newCondition 方法都将获得不同的 Condition 对象

 

除了上述强大的加锁与等待、唤醒接口外,ReentrantLock 还提供了丰富而强大的查询接口,让你了解到锁相关的各种情况:

int getHoldCount(); // 获取当前线程持有该锁的次数 boolean isHeldByCurrentThread(); // 判断当前线程是否持有该锁 boolean isLocked(); // 获取锁状态是否为加锁状态 boolean isFair(); // 当前锁是否是公平锁 Thread getOwner(); // 获取持有锁的线程 boolean hasQueuedThreads(); // 判断当前锁是否有线程在等待 boolean hasQueuedThread(Thread thread); // 判断指定线程是否在等待该锁 int getQueueLength(); // 获取正在等待该锁的线程数量 boolean hasWaiters(Condition condition); // 判断是否有线程等待在该 Condition 对象上 int getWaitQueueLength(Condition condition); // 获取等待在该 Condition 对象的线程数

 

 






技术帖      技术分享      线程            java      condition      reentrantlock      等待      唤醒      interrupt     


京ICP备15018585号