一、 ReentrantLock原理开篇
ReentrantLock(可重入锁)在平时并发编程时用的很多,基于它实现的独占锁、共享锁功能强大,内部还实现了公平锁、非公平锁、满足了可重入性等等,那么这么强大的锁同步机制,它的内部又是怎么构造实现的呢?我们先从它的类图分析
二、AQS
2.1 类图分析
从类图中我们可以看到,ReentrantLock的实现似乎是傍上了“大佬”,站在了AbstractQueuedSynchronizer这个类的“肩膀上”,那么这个所谓的AQS又是什么来头?
2.2 AQS分析
2.2.1 概述
AbstractQueueSysnhronizer:阻塞式锁和相关的同步器工具的框架
2.2.2 特点
-
用state属性来表示资源的状态(分独占式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
- getState 获取state 状态
- seteState 设置state 状态
- compareAndSetState - cas 机制设置 state 状态
- 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
-
提供了基于FIFO的等待队列,类似于Monitor 的 EntryList
-
条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor 的 WaitSet
AQS中的核心变量:
// 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
private transient volatile Node head;
// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
private transient volatile Node tail;
// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
private volatile int state;
// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
// if (currentThread == getExclusiveOwnerThread()) {state++}
private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
子类主要实现这样一些方法(默认抛出UnSpupportedOperationException)
tryAcquire(int); // 尝试获取独占锁,可获取返回true,否则false
tryRelease(int); // 尝试释放独占锁,可释放返回true,否则false
tryAcquireShared(int); // 尝试以共享方式获取锁,失败返回负数,只能获取一次返回0,否则返回个数
tryReleaseShared(int); // 尝试释放共享锁,可获取返回true,否则false
isHeldExclusively(); // 判断线程是否独占资源
2.2.3 对AQS理解
AQS作为阻塞式锁和相关同步器工具的框架,提供了强大的锁实现功能,基于AQS我们可以方便的开发出一套个性化的锁同步机制。而ReentrantLock的实现就主要依赖了AQS所提供的state状态变量以及内部实现的双向链表的机制,以CAS的方式为我们提供了一套完备的锁机制。
三、ReentrantLock原理
3.1 非公平锁实现
1)默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//NonfairSync继承于AQS
2)加锁流程
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))//修改aqs状态
setExclusiveOwnerThread(Thread.currentThread());
else
//有竞争时进入acquire
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
3)锁竞争情况(失败)
3.1)无竞争时:
- 设置state为1
- 并设置exclusiveOwnerThread为当前线程
3.2)第一个竞争出现时:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//acquireQueued(addWaiter(Node.EXCLUSIVE)尝试创建节点对象,放入等待队列中去
Thread-1竞争线程执行步骤:
-
CAS尝试将state由0修改为1,结果失败,进入acquire()方法
-
由if判断进入tryAcquire逻辑,这时state已经是1,结果依然失败
-
接下来进入addwaiter逻辑,构造Node队列
//构造Node队列 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
- 图中黄色三角表示该Node的waitStatus状态,其中0为默认正常状态
- Node节点的创建是懒惰的
- 其中第一个Node被称为Dummy(哑元)或哨兵,用来占位,并不关联线程
- 当前节点进入 acquireQueued逻辑
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环不断尝试获取锁
for (;;) {
//获取node的前驱节点
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- acquireQueued会在一个死循环中不断尝试获取锁,失败后进入park阻塞
- 如果自己是紧邻着head(排第二位),那么再次tryAcquire尝试获取锁,当然这时state仍为1,失败
- 进入 shouldParkAfterFailedAcquire 逻辑
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //Node.SIGNAL=-1
//前驱已是-1,直接返回true
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//waitStatus改为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
-
将前驱node,即head的waitStatus改为-1(-1表示有责任唤醒后继节点),这次返回false
-
shouldParkAfterFailedAcquire 逻辑执行完毕回到for循环重新执行,这次shouldParkAfterFailedAcquire 返回的是true(因为前驱节点已为-1),执行&&后半部分
-
进入parkAndCheckInterrupt逻辑,将线程打断(图中以灰色表示)
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
3.3)多个线程竞争失败后
- 此时多个竞争失败线程形成双向链表,并被park,交由前驱节点唤醒
4)锁释放流程
public final boolean release(int arg) {
if (tryRelease(arg)) { //尝试释放
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); //唤醒最近的后继节点
return true;
}
return false;
}
-
Thread-0释放锁,进入tryRelease流程,如果成功
- 设置exclusiveOwnerThread为null
- state设置为0
- 如果当前节点不为null,且head的waitStatus的值不是0,说明有后继节点,进入unparkSuccessor逻辑
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) //ws是-1,重置为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; } //有后继的unpark唤醒策略 if (s != null) LockSupport.unpark(s.thread); }
5)阻塞线程唤醒
因为是非公平锁,所以存在被抢占锁的情况
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环不断尝试获取锁
for (;;) {
//获取node的前驱节点
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//此处被唤醒
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
5.1)此时没有新来线程竞争锁
- 后继被唤醒的线程在之前的park位置唤醒后再次执行for循环,获取锁成功,进入if代码块
- 设置当前节点为头结点,并断开初始的头节点
5.2)此时有新来线程竞争锁
可能会竞争失败,重新进入阻塞
3.2 可重入原理
1)可重入加锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//尝试cas获取锁,这里体现了非公平性:不去检查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;
}
}
- state为0,尝试加锁
- state不为0,判断当前加锁线程是否是获取锁线程,是则将state加1即可,否则返回false
2)可重入解锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
- state不断减,到0,则释放锁
3.3 可打断模式
1)不可打断模式
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; // help GC
failed = false;
return interrupted; //返回打断标记
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; //能进入if说明该线程被打断了,设置打断标记为true,这样即使获取锁它依然具有打断标记
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
//清除打断标记(防止被打断后导致线程无法被park),如果被打断过,返回true,否则返回false,
return Thread.interrupted();
}
简单来说,就是让在阻塞时的线程不会在获取锁之前被打断,而是在获取锁之后带上打断标记运行
2)打断模式
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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException(); //与上述代码的唯一区别,这里抛异常,直接在阻塞时就被打断
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.4 公平锁实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//该if就是公平锁和非公平锁最大区别
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() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//h!=t说明链表中有节点
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
3.5 条件变量实现
1)await
每个条件变量其实就是对应着一个等待队列,其实现类是ConditionObject
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
long savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
进入addConditionWaiter逻辑,创建的也是一个双向链表
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) { //Node.CONDITION=-2
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
为什么是调用fullRelease呢?
因为拿到锁的线程可能会发生重入现象,通过fullRelease完全释放锁
unpark AQS队列中的下一个节点,竞争锁,加锁没有其他竞争加锁,那么Thread-1竞争成功
2)signal
public final void signal() {
//是否是持有锁的
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
//脱离ConditionObject的等待队列
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//转移到等待队列对尾
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
执行的transferForSignal逻辑,将该Node加入到AQS队尾,将Thread-0的WaitStatus改为0,Thread-3的waitStatus改为-1
四、总结
理解AQS中基于内存共享的变量模型(最根本是volatile的原理应用),搭配合理的数据结构(双向链表),就能够理解我们AQS工具类的根本。在这个根本上合理组织和构建,ReentrantLock也就诞生了,这种基于工具类构建增强类,在共有基础上衍生出更丰富和更独特的功能的方式,在面向接口编程中比比皆是,需要我们好好体会!