1、概述
ReentrantLock 主要利用 CAS + AQS队列来实现,通过重写了 AQS 的 tryAcquire 和 tryRelease方法实现的 lock 和 unlock。
- Sync:抽象类,是 ReentrantLock 的内部类,继承自 AQS,实现了释放锁的操作(tryRelease()方法),并提供了 lock 抽象方法,由其子类实现。
- NonfairSync:是 ReentrantLock的内部类,继承自 Sync,非公平锁的实现类。
- FairSync:是 ReentrantLock 的内部类,继承自 Sync,公平锁的实现类。
AQS内部维护着一个 FIFO(先进先出) 队列,该队列就是 CLH 同步队列。AQS 依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS 则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到 CLH 同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
ReentrantLock 的基本实现可以概括为:先通过 CAS 尝试获取锁。如果此时已经有线程占据了锁,那就加入 AQS 队列并且被挂起。当锁被释放之后,排在 CLH 队列队首的线程会被唤醒,然后 CAS 再次尝试获取锁。在这个时候,如果:
- 非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;
- 公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。
2、源码分析
2.1 构造函数
public class ReentrantLock implements Lock, java.io.Serializable{
// 默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//指定是当前是公平锁 还是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
2.2 非公平锁源码分析
2.2.1 加锁
public void lock() {
//当为非公平锁时 这里的lock会走NonfairSync下的lock函数
sync.lock();
}
final void lock() {
//利用CAS更新当前锁的状态
if (compareAndSetState(0, 1))
//设置当前线程为独占锁的拥有者
setExclusiveOwnerThread(Thread.currentThread());
else
//如果CAS更新失败 获取锁
acquire(1);
}
//如果CAS更新成功,设置当前线程为独占锁的拥有者
//AbstractOwnableSynchronizer中方法;
//AbstractOwnableSynchronizer把AQS包了一层;主要提供了一些获取、设置当前独占锁拥有者
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
首先用一个 CAS 操作,判断 state 是否是0(表示当前锁未被占用),如果是 0 则把它置为 1,并且设置当前线程为该锁的独占线程,表示获取锁成功。当多个线程同时尝试占用同一个锁时,CAS操作只能保证一个线程操作成功,剩下的只能乖乖的去排队啦。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取state变量值
int c = getState();
if (c == 0) { //没有线程占用锁
if (compareAndSetState(0, acquires)) {
//占用锁成功,设置独占线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) { //当前线程已经占用该锁,重入锁与原理
// state++
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
// 更新state值为新的重入次数
setState(nextc);
return true;
}
//获取锁失败
return false;
}
非公平锁 tryAcquire 的流程:
- 检查state字段,若为 0,表示锁未被占用,那么尝试占用
- 若不为 0,检查当前锁是否被自己占用,若被自己占用,则更新state字段,表示重入锁的次数。
- 如果以上两点都没有成功,则获取锁失败,返回false。
acquire流程:
- 调用自定义同步器的 tryAcquire() 尝试直接去获取资源,如果成功则直接返回;
- 没成功,则 addWaiter() 将该线程加入等待队列的尾部,并标记为独占模式;
- acquireQueued() 使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源,获取到资源后才返回,返回false。如果在等待过程中被中断过,那么会从park() 中醒过来,发现拿不到资源,从而继续进入 park() 等待,等到轮到自己时才返回 true。
- 如果线程在等待过程中被中断过,会调用cancelAcquire()方法取消当前节点的状态,如果其前置节点释放了锁,当前取消节点还要唤醒后续节点,到了acquire方法,当前节点只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
2.2.2 解锁
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒等待队列里的下一个线程
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// 计算释放后state值
int c = getState() - releases;
// 如果不是当前线程占用锁,那么抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 锁被重入次数为0,表示释放成功
free = true;
// 清空独占线程
setExclusiveOwnerThread(null);
}
// 更新state值
setState(c);
return free;
}
tryRelease 的过程为:当前释放锁的线程若不持有锁,则抛出异常。若持有锁,计算释放后的state值是否为0,若为0表示锁已经被成功释放,并且则清空独占线程,最后更新state值,返回free。
release流程:尝试释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。
2.2.3 总结
调用 lock 方法,当为非公平锁时,只要有线程过来就尝试获取锁,
- 如果获取成功
- AQS的state==0 并且CAS写入成功,将自身设置为独占锁的拥有者
- state != 0 但当前独占锁就是自身(表示重入),将state累加;
- 如果获取失败就将自己设置到 AQS 队列的尾部,等待唤醒;
2.3 公平锁源码分析
当为公平锁时,与非公平锁的主要区别在于公平锁在执行 tryAcquire 时,需要加一个判断(当前节点是否还有其他节点),如果有其它节点则将自身添加到队列中等待唤醒;
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 先检查 AQS 队列中是否有前驱节点, 没有才去竞争,非公平锁是直接竞争
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;
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// h != t 时表示队列中有 Node
return h != t &&
// (s = h.next) == null 表示队列中还有没有老二
((s = h.next) == null ||
// 或者队列中老二线程不是此线程
s.thread != Thread.currentThread());
}
2.4 可重入原理
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)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 支持锁重入, 只有 state 减为 0, 才释放成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
2.5 可打断原理
2.5.1 不可打断模式
默认是不可打断模式,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 需要获得锁后, 才能返回打断状态
if (p == head && tryAcquire(arg)) {
setHead(node)
p.next = null;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2.5.2 可打断模式
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// AQS 内部方法
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
// 如果没有获得到锁, 进入
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// 在 park 过程中如果被 interrupt 会抛出异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}