回答
ReentrantLock
是一种可重入的排它锁,主要用来解决多线程对共享资源竞争的问题。ReentrantLock
提供了比 synchronized 更加灵活、更强大的锁机制。
它的核心特性有如下几个:
- 可重入性:
ReentrantLock
是一个可重入锁,也就是说同一个线程可以多次获取同一个锁。 - 公平锁和非公平锁:
ReentrantLock
支持公平和非公平两种方式。- 公平锁意味着等待时间最长的线程将会首先获得锁
- 非公平锁可能会让新请求的线程优先于正在等待的线程获得锁。
ReentrantLock
的底层实现是基于 AbstractQueuedSynchronizer
框架,即 AQS
。AQS
使用一个 volatile 整数(state)来表示同步状态,并维护了一个等待线程的队列。在 ReentrantLock
中,当线程尝试获取锁时,如果锁未被占用(即 state 为 0),该线程将成功获取锁并将 state 值 + 1。如果锁已被其他线程占用,尝试获取锁的线程将被加入到 AQS
维护的等待队列中,并处于阻塞状态。当锁被释放时,AQS
会根据锁的公平性或非公平性策略选择下一个获取锁的线程,并将其从队列中移除和唤醒。由于其内部采用了无锁模式的 CAS(Compare-And-Swap)操作和 volatile 变量,ReentrantLock
能有效地减少锁的竞争,并提高并发性能。同时,ReentrantLock
的可重入特性允许线程在持有锁的情况下重复获取同一锁,从而在递归调用等情况下避免死锁。
详解
ReentrantLock 简介
ReentrantLock
实现 Lock 接口,是一种可重入的独占锁。相比于 synchronized 同步锁,ReentrantLock
更加灵活,拥有更加强大的功能。它拥有如下几个特性:
- 可重入性:
ReentrantLock
是一个可重入锁,这意味着同一个线程可以多次获得同一个锁。如果某个线程已经持有锁,它可以再次请求并获得锁,而不会被阻塞。 - 公平性选项:
ReentrantLock
支持公平和非公平两种方式。在创建ReentrantLock
实例时,我们可以选择是创建一个公平锁还是非公平锁。对于公平锁,按照线程等待的顺序来分配锁;而非公平锁可能会优先分配给最近请求锁的线程。 - 锁定与解锁的控制:与 synchronized 不同,
ReentrantLock
提供了显式的锁定和解锁方法。通过lock()
方法来获取锁,并通过unlock()
方法来释放锁,相比 synchronized ,这种方式提供了更灵活的锁管理方式。 - 支持超时:
ReentrantLock
提供了带有超时功能的锁获取方法,如tryLock()
,允许线程在指定时间内尝试获取锁,超时未获取则放弃,这有助于防止死锁。 - 条件变量:
ReentrantLock
关联了Condition
类的对象,可以让线程在某些条件下挂起和被唤醒。 - 中断响应:
ReentrantLock
提供了能让线程在等待锁的过程中响应中断 的方法:lockInterruptibly()
。该方法允许线程在等待锁的过程中,如果被中断,则抛出一个InterruptedException
。
下图是它的 UML 结构类图:
ReentrantLock
实现 Lock 接口,内部有一个 Sync 的引用,Sync 继承 AbstractQueuedSynchronizer ,是 ReentrantLock 实现锁机制的基石和根本所在。FairSync 和 NonFairSync 分别对应公平锁和非公平锁,他们都继承 Sync。
ReentrantLock
内部有一个非常重要的成员变量:
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
这个变量为 ReentrantLock
提供了锁机制的实现,我们就可以认为它就是锁。这个变量用来指向 Sync 的子类,即 FairSync 和 NonFairSync,在构造 ReentrantLock
实例的时候就确定 sync 是 FairSync 还是 NonFairSync:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
源码分析
公平锁
ReentrantLock
默认非公平锁,我们可以调用构造来构建公平锁:new ReentrantLock(true)
:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
获取锁
调用 lock()
获取公平锁:
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg)
:尝试获取锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取同步状态 state
int c = getState();
// = 0 表示该锁还没被占用
if (c == 0) {
// hasQueuedPredecessors()判断同步队列中是否有线程在等待
// compareAndSetState() 通过 CAS 的方式来获取锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 获取锁成功
setExclusiveOwnerThread(current);
return true;
}
}
// c != 0 表示当前锁已有线程占用
else if (current == getExclusiveOwnerThread()) {
// 重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
流程图如下:
hasQueuedPredecessors()
是用来实现公平锁的机制:
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾节点
Node h = head; //头节点
Node s;
//头节点 != 尾节点
//同步队列第一个节点不为null
//当前线程是同步队列第一个节点
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
释放锁
调用 unlock()
方法释放锁:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
AbstractQueuedSynchronizer.Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒下一个节点
unparkSuccessor(h);
return true;
}
return false;
}
调用 tryRelease()
释放锁,由于 ReentrantLock
是可重入的,所以当前线程不一定已完全释放锁资源,如果只释放了部分,则返回 false,否则返回 true。返回 true,则需要唤醒下一个节点(线程)。
protected final boolean tryRelease(int releases) {
// state -1
int c = getState() - releases;
// 释放锁的线程和持有锁的线程要是同一个
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state = 0,说明锁资源已完全释放,返回 true,同时将锁的持有者置为空
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
流程图如下:
非公平锁
ReentrantLock
默认是非公平锁,我们有两种方式创建非公平锁 ReentrantLock
:
new ReentrantLock()
new ReentrantLock(false)
获取锁
final void lock() {
// 首先尝试获取锁
if (compareAndSetState(0, 1))
// 成功,将锁的持有者设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
非公平锁首先会进行一次快速获取锁,如果获取成功,就将锁的持有者设置为自己。如果这次快速获取锁失败,则调用 acquire()
参与锁竞争:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这里和公平锁一样,调用 tryAcquire()
获取锁,如果失败则添加到 CLH 队列去。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
nonfairTryAcquire()
与 公平锁的 tryAcquire()
几乎一模一样,就少了一个 hasQueuedPredecessors()
的判断,因为非公平锁不需要判断当前线程是否为首节点线程,谁抢到就是谁的,简单粗暴。
流程图如下:
非公平锁的释放机制和公平锁一样,大明哥这里就不做介绍了。