ReentrantLock有两种锁:NonfairSync(非公平锁),FairSync(公平锁),本文基于NonfairSync分析ReentrantLock的源码。
使用示例
static ReentrantLock lock = new ReentrantLock();
static void testReentrantLock() {
lock.lock();
try {
System.out.println("同步代码块");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(ReentrantLockDemo::testReentrantLock, "Thread-01").start();
new Thread(ReentrantLockDemo::testReentrantLock, "Thread-02").start();
}
线程Thread-01和Thread-02只有一个能进入同步代码块,另一个线程阻塞在lock.lock()的位置,只有进入同步代码块的线程执行lock.unlock()之后,阻塞线程才能进入同步代码块。
源码分析
获取锁原理分析
通过CAS操作,获取共享变量(state)的控制权;如果获取成功(state:0->1),该线程即可执行同步代码块的内容,否则就要进入AQS队列,等待获取锁的线程释放锁(state:1->0)后竞争对共享变量的控制。
public void lock() {
sync.lock();
}
final void lock() {
// 不关心AQS队列是否存在线程排队,直接进行CAS操作,尝试获取共享变量的控制,获取成功就说明获取锁成功
// CAS操作是线程安全的,可以保证只有一个线程能执行成功
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// CAS操作失败,需要进行后续操作
acquire(1);
}
public final void acquire(int arg) {
// tryAcquire:
// 1、再一次进行CAS操作,尝试获取共享变量的控制
// 2、判断是否为重入锁,如果是重入锁,直接获取共享变量的控制
// addWaiter(Node.EXCLUSIVE):将当前线程添加到AQS队列(Node.EXCLUSIVE:表示为互斥锁)
// acquireQueued:自旋,等待获取获取锁
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();
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) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
private Node addWaiter(Node mode) {
// 初始化一个Node节点,mode此时为null,只要知道该标识表名为互斥锁即可
Node node = new Node(Thread.currentThread(), mode);
// tail指向AQS的尾部节点
Node pred = tail;
// AQS队列不为空
if (pred != null) {
// 将新增节点的前置节点设置为AQS的尾部节点
node.prev = pred;
// 将tail指向新增的节点
if (compareAndSetTail(pred, node)) {
// 将AQS的尾部节点的后置节点设置为新增的节点
pred.next = node;
return node;
}
}
// AQS队列为空,执行此处操作
enq(node);
return node;
}
private Node enq(final Node node) {
// 自旋,直到新增节点插入到AQS队列
for (;;) {
// tail指向AQS的尾部节点
Node t = tail;
// AQS队列为空,需要初始化AQS队列
if (t == null) {
// 由于此处线程不安全,通过CAS操作,保证只有一个线程能执行初始化操作
// head指向新增的头部节点(该节点没有存储任务信息,只是表示一个开始节点)
if (compareAndSetHead(new Node()))、
// tail也指向新增的头部节点
tail = head;
} else {
// 此时说明AQS队列已经初始化成功
// 将当前节点的前置节点设置为AQS队列的尾部节点
node.prev = t;
// 由于此处不是线程安全的,通过CAS操作,保证只有一个线程能够将tail引用指向新增的节点,其他线程等待下一次循环,重新添加到AQS队列的尾部
if (compareAndSetTail(t, node)) {
// 将AQS的尾部节点的后置节点设置为新增的节点
t.next = node;
return t;
}
}
}
}
AQS队列结构图
/**
* 自旋,如果需要挂起时,就将当前线程挂起,等待唤醒
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
// 线程中断标识,用于在线程挂起被唤醒后,获取在挂起的过程中当前线程是否被中过的标识
boolean interrupted = false;
for (;;) {
// 获取当前线程的前置节点
final Node p = node.predecessor();
// 如果前置节点为head节点,此时可以尝试获取锁
if (p == head && tryAcquire(arg)) {
// 将当前节点设置为head节点
setHead(node);
// 原head节点等待垃圾回收
p.next = null;
failed = false;
return interrupted;
}
// 尝试获取锁失败后,判断是否需要将线程挂起
// shouldParkAfterFailedAcquire:判断当前线程是否需要挂起(ws表示前置节点的状态)
// ws = -1:表示当前线程需要被挂起
// ws > 0:前置节点线程被cancel,此时需要移出AQS队列中所有被取消的线程
// ws为其他:通过CAS操作更新前置节点的状态为-1,等待下次循环再次判断是否需要被挂起
// parkAndCheckInterrupt:线程挂起,等待进入代码块的线程执行unlock操作后根据AQS入队顺序依次唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前置节点状态
int ws = pred.waitStatus;
// -1 表示当前线程需要被挂起
if (ws == Node.SIGNAL)
return true;
// 0 表示前置节点被cancel,此时遍历AQS队列当前节点前面所有的节点,清理所有被cancel的节点
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 更新前置节点为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
// 线程挂起,让出CPU
LockSupport.park(this);
// 线程被唤醒,获取在挂起的过程中,当前线程是否被中断的标识
return Thread.interrupted();
}
释放锁原理分析
执行一次unlock方法,state值减1,当state=0时,获取AQS的head节点,并唤醒head的节点的后置节点所属的线程,并尝试获取共享变量的控制。
public void unlock() {
// 公平锁和非公平锁的释放原理一致,实现逻辑由AbstractQueuedSynchronizer类实现
sync.release(1);
}
// tryRelease:state值减1,如果state最终结果为0,说明锁完全释放成功,唤醒AQS队列中的第一个节点
if (tryRelease(arg)) {
// 获取AQS队列的head节点
Node h = head;
// h !=null 说明AQS队列不为空
// h.waitStatus != 0 说明AQS队列存在需要被唤醒的线程(阻塞线程进入AQS队列时,会将前置节点的waitStatus值设置为-1)
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
protected final boolean tryRelease(int releases) {
// 共享变量释放releases个单位
int c = getState() - releases;
// 如果当前线程不是获取锁的线程,说明存在其他线程非法调用释放锁的操作,直接抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// free主要用来标识当前线程是否完全释放对锁的控制(重入锁会导致state值增加,每重入一次,state就加1)
boolean free = false;
if (c == 0) {
// 释放共享变量
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
// 前置节点waitStatus值设置为默认值
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 获取需要唤醒的节点
Node s = node.next;
// s == null 说明没有需要唤醒的线程
// s.waitStatus > 0 说明需要唤醒的线程已经被cancel了,此时从AQS队列的尾部往前遍历,获取最后一个需要被唤醒的线程节点
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);
}
AQS的第一个节点被唤醒。此时原有的head节点等待垃圾回收,第一个阻塞线程节点变为head节点,并获取了共享变量的控制,进入同步代码块。