1. 概述
此前的文章中我们介绍了 synchronized 锁的使用及实现原理: synchronized 的使用及实现原理 文中,我们看到,jdk1.6 对 synchronized 锁进行了一系列的优化,使得我们再也不用为 synchronized 锁的性能担忧,在此之前,synchronized 锁因为其性能问题是很少被使用的,那时,最常用的锁结构就是今天我们要介绍的 ReentrantLock 锁。 虽然时至今日,优化后的 synchronized 锁的性能已经与 ReentrantLock 接近,但 ReentrantLock 仍然具备着 synchronized 所不具备的很多优势: 1. 吞吐量更高、执行效率更高 2. 支持公平锁、非公平锁两种模式 3. 更加灵活精准的等待与唤醒 4. 可中断锁、轮询锁 5. 可伸缩性强 6. 支持条件变量 因此,在高度竞争的并发环境下,以及较为复杂的使用场景中,ReentrantLock 都是 synchronized 的有力替代品。
本文,我们就来介绍一下 ReentrantLock 的用法,下一篇文章中,我们详细的剖开源码解析 ReentrantLock 的实现原理。
2. ReentrantLock 的基本使用 — 加锁和解锁
ReentrantLock 类提供了最基本的加锁和解锁方法:
代码语言:javascript复制public void lock();
public void unlock();
我们通过最基本的加锁方法实现上一篇日志中提到的自增方法锁。
代码语言:javascript复制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 关键字实现了相同的效果:
代码语言:javascript复制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 还是最好的选择。
3. 公平模式与非公平模式
使用 synchronized 锁是不保证等待的线程获取到锁的顺序的,这就是非公平锁,除了默认的非公平锁构造方法外,ReentrantLock 还提供了一个带有 boolean 参数的构造方法:
代码语言:javascript复制public ReentrantLock(boolean fair);
如果传入参数为 true,则会创建公平锁,所谓的公平锁,就是保证了先进入等待的线程一定先获取到锁。
可以通过 isFair 方法查询 ReentrantLock 对象是否是公平锁:
代码语言:javascript复制public final boolean isFair();
4. 非阻塞式锁、时间限制锁与可中断锁
ReentrantLock 提供了 tryLock 方法与 lockInterruptibly 方法用来实现非阻塞式锁、时间限制锁与可中断锁。
代码语言:javascript复制public boolean tryLock() // 尝试获取锁,立即返回获取结果
public boolean tryLock(long timeout, TimeUnit unit) // 尝试获取锁,最多等待 timeout 时长
public void lockInterruptibly() // 可中断锁,调用线程 interrupt 方法,则锁方法抛出 InterruptedException
以上三种锁方式,synchronized 都是无法实现的,正如我们上一篇日志中所提到,interrupt 方法是不会中止正在等待获取 synchronized 锁的线程的。
5. Condition 与线程等待
Object 类提供了只能在 synchronized 代码块中使用的 wait、notify、notifyAll,ReentrantLock 也拥有类似但更为强大的等待和唤醒机制,这就是通过 Condition 对象唤醒的。
5.1. Condition
通过 newCondition 方法,可以创建出 Condition 对象。
代码语言:javascript复制public Condition newCondition();
Condition 接口提供了如下的方法:
代码语言:javascript复制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 对象。
6. 强大而丰富的查询接口
除了上述强大的加锁与等待、唤醒接口外,ReentrantLock 还提供了丰富而强大的查询接口,让你了解到锁相关的各种情况:
代码语言:javascript复制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 对象的线程数