类结构
ReentrantLock
是一个可重入独占锁。可重入的意思是同一个线程可以对同一个共享资源进行重复的加锁和释放锁,独占就是任何时刻只允许一个线程占有锁。ReentrantLock
有公平锁和非公平锁两种实现,均以内部类的方式来实现,如下是ReentrantLock
内部类的层次结构:
构造方法
ReentrantLock
有两个构造函数,带参数的构造函数当参数为true
时将创建公平锁,公平锁保证线程按照先后顺序来获取锁。
//默认是非公平锁
public ReentrantLock() {
//Sync是ReentrantLock的内部类
sync = new NonfairSync();
}
//true是公平锁,false是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
lock:获取锁
ReentrantLock
调用lock
方法时会调用的是它的内部类Sync
。
public void lock() {
sync.lock();
}
Sync
继承自AQS
并派生出公平锁和非公平锁,如下是公平锁与非公平锁lock
方法实现:
- 非公平锁
//NonfairSync
final void lock() {
//尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//获取锁失败,调用AQS的acquire方法
acquire(1);
}
- 公平锁
//FairSync
final void lock() {
//直接调用AQS的acquire方法
acquire(1);
}
上面是公平锁与非公平锁lock
方法的实现,可以看到非公平锁会优先去尝试获取锁,体现了非公平的原则。
公平锁和非公平锁最终都会调用AQS
的acquire
方法,acquire
方法调用时会传递一个int
参数,arg=1
表示加一次锁。
//AbstractQueuedSynchronizer
public final void acquire(int arg) {
//尝试获取锁
if (!tryAcquire(arg) &&
//获取锁失败,将线程添加到AQS等待队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
AQS
中的tryAcquire
是一个空方法,在公平锁与非公平锁种有不同的实现,如下分析
非公平锁中tryAcquire的实现
非公平锁中直接调用Sync
的nonfairTryAcquire
方法
//NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
nonfairTryAcquire
,根据方法名字就能猜出是非公平锁的实现。在非公平策略下,线程请求锁时无需判断AQS
等待队列中是否有阻塞线程在等待获取锁,而是直接检查锁是否被线程占用,如果锁没有被线程占用,调用cas
方法将acquires
参数值设置到锁状态state
内存地址上,然后判断cas
返回值,true
说明当前请求锁的线程获取锁成功,将该线程设置为锁的持有线程。
如果锁已经被线程占用了,则需要去判断获取锁的线程与锁的持有线程是否是同一条线程,如果是,则直接将锁状态state
加 1(可重入的特性)。
//覆盖AQS tryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//0 说明锁还没有被占用
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;
}
公平锁中tryAcquire的实现
公平策略下,线程在获取锁时需要考虑AQS
等待队列,当队列中有线程等待获取锁时,请求锁的线程将被追加到AQS
等待队列的末尾并进入阻塞状态。
static final class FairSync extends Sync {
//覆盖AQS tryAcquire方法
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//当前锁没有线程持有
if (c == 0) {
//判断等待队列中是否有等待获取锁的线程
if (!hasQueuedPredecessors() &&
//没有,则尝试去获取锁
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//可重入锁的特性
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;
// 头节点要么是空节点,要么就是当前正在执行的线程
//h == t时,队列中一定没有阻塞的线程,线程可以尝试去获取锁
//当h != t队列中有等待线程,需要判断Thread.currentThread()是否是队列中的第一个线程
// (s = h.next) != null && s.thread == Thread.currentThread() 表示当前线程是队列中的第一个等待线程
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
自旋获取锁
public final void acquire(int arg) {
//tryAcquire,尝试获取锁.
if (!tryAcquire(arg) &&
//如果获取锁失败,将线程封装成Node节点添加到等待队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
线程调用tryAcquire
获取锁失败后将调用addWaiter
方法将线程封装成AQS Node
节点添加到AQS
等待队列中,然后调用acquireQueued
自旋尝试获取锁。
公平锁与非公平锁的实现逻辑是一样的,这一块的源码分析请参考AQS源码分析。
释放锁
ReentrantLock
是一个可重入锁,存在一个线程多次请求锁的情况,线程请求一次锁时,锁状态state
的值+1
。同样线程释放锁时,调用一次release
,锁状态state
的值-1
。
//ReentrantLock释放锁
public void unlock() {
//调用AQS的release方法
sync.release(1);
}
//AbstractQueuedSynchronizer
public final boolean release(int arg) {
//1.尝试释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//2.唤醒后续节点
unparkSuccessor(h);
return true;
}
return false;
}
1.尝试释放锁。
AbstractQueuedSynchronizer
中tryRelease
是一个空方法,方法的具体实现在ReentrantLock
的内部类Sync
中实现。
//Sync
protected final boolean tryRelease(int releases) {
//可重入锁,getState() 可能大于1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//c == 0 说明线程已经全部释放完锁了,可以唤醒等待队列中的线程
free = true;
//将锁的持有线程设置为null
setExclusiveOwnerThread(null);
}
//更新锁状态
setState(c);
return free;
}
2.唤醒后续节点。
如果一个线程释放了它持有的全部锁,将调用unparkSuccessor
方法唤醒头节点的后继节点。在唤醒节点前,需要判断节点的状态是否已经被取消。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//过滤掉取消掉节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
//最近的一个未被取消的节点
if (t.waitStatus <= 0)
s = t;
}
//唤醒
if (s != null)
LockSupport.unpark(s.thread);
}
总结
ReentrantLock
是一个可重入独占锁。同一个线程可以对同一个共享资源进行重复的加锁和释放锁,任何时刻只允许一个线程占有锁。ReentrantLock
有公平锁和非公平锁两种实现,它们的区别在于当锁状态state
处于无锁状态时,公平锁会检查AQS
等待队列中是否存在阻塞的线程,如果有,那么公平锁会放弃尝试获取锁,保证线程获取锁的先后顺序;非公平锁会直接尝试获取锁,不会检查等待队列。
当线程尝试获取锁失败后,都将被封装成Node
节点保存到阻塞队列中,等待它的前置节点唤醒它,或者被中断,公平锁和非公平锁在这一点上是一样的。