ReentrantLock
1. ReentrantLock简介
ReentrantLock意思是"可重入锁",可理解为可以重复使用的锁,ReentrantLock是实现了Lock接口的类,并且ReentrantLock提供了更多的方法。ReentrantLock还提供了公平锁也非公平锁的选择,构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。
公平锁与非公平锁的区别在于公平锁的锁获取是有顺序的,因而非公平锁的效率更高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。
2. ReentrantLock特性
- 可重入性: 如果一个线程已经持有了某个锁,那么它可以再次调用lock()方法而不会被阻塞。锁的持有计数会在每次成功调用lock()方法时递增,并在每次unlock()方法被调用时递减。
- 公平性: ReentrantLock提供了一个公平锁的选项。公平锁会按照线程请求锁的顺序来分配锁,而不是像非公平锁那样允许线程抢占已经等待的线程的锁。公平锁可以减少“饥饿”的情况,但也可能降低一些性能。
- 可中断性:ReentrantLock的获取锁操作(
lockInterruptibly()
方法)可以被中断。这提供了另一个相对于synchronized关键字的优势,因为synchronized不支持响应中断。 - 条件变量: ReentrantLock类中还包含一个Condition接口的实现,该接口允许线程在某些条件下等待
await()
或唤醒signal()
。
3. ReentrantLock的使用
public class Demo_ReentrantLock {
static class Ticketer {
private int num = 30;
// 创建可重入锁
private ReentrantLock lock = new ReentrantLock();
public void sale() {
// 上锁
lock.lock();
try {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖了1张, 还剩" + --num);
}
}finally {
// 解锁
lock.unlock();
}
}
}
public static void main(String[] args) {
Ticketer ticketer = new Ticketer();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticketer.sale();
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticketer.sale();
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticketer.sale();
}
}, "CC").start();
}
}
运行结果: 线程调用
start()
不一定马上执行,可能立即执行可能隔一会儿执行。
查看start()
方法源码:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
start0()
是一个本地方法,操作系统完成线程的创建,已经超出JVM的管理范围, 所以线程的启动时间不确定。
4. ReentrantLock的实现原理
ReentrantLock的实现依赖于内部的Sync类,这个类是AbstractQueuedSynchronizer(AQS)的一个实现。AQS是Java并发库中许多同步工具(包括Semaphore、CountDownLatch和CyclicBarrier等)的核心。
AQS使用一个int类型的变量来表示同步状态,ReentrantLock用它来表示锁的持有计数和持有线程的信息。当计数为0时,表示锁未被任何线程持有。当一个线程首次成功获取锁时,JVM会记录这个锁的持有线程,并将计数器设置为1。如果同一个线程再次请求这个锁,它将能够再次获得这个锁,并且计数器会递增。当线程释放锁时(通过调用unlock()方法),计数器会递减。如果计数器递减为0,则表示锁已经完全释放,其他等待的线程有机会获取它。
AQS内部维护着一个FIFO双向队列,该队列就是CLH同步队列。AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败(就是图中加锁没成功)时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。公平锁与非公平锁的区别在于获取锁的时候是否按照AQS的队列FIFO的顺序来。