在分析ReentrantLock前,还是需要直到AQS是什么的。AbstractQueuedSynchronizer队列同步器,简称AQS,是用来构建锁或者其他同步组件的基础框架,常用的ReentrantLock、ConuntDownLatch、Semaphore等JUC包下的组件都是基于AQS来实现的。
AQS使用了一个int的成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。AQS定义了两种资源共享方式,独占和共享。本文说是分析ReentrantLock,实际上是借着它来分析AQS的独占模式。
还是先来看看有哪些重要的属性和辅助方法需要知道。
重要的属性
#AbstractQueuedSynchronizer
//队列头节点
private transient volatile Node head;
//队列尾节点
private transient volatile Node tail;
//同步状态, 0表示未加锁, >0表示已经加锁
private volatile int state;
AQS提供了三个方法对应state的读写操作
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
//cas 写
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
Node是AQS的静态内部类,里面也有一些重要的属性,因为本文是分析独占模式,所以只注释了和独占模式有关的代码。
static final class Node {
static final Node SHARED = new Node();
//独占模式
static final Node EXCLUSIVE = null;
//表示当前节点处于取消状态
static final int CANCELLED = 1
//表示当前节点需要唤醒它的后继节点
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// ==0 默认状态
// >0 取消状态
// ==-1 表示当前节点释放锁后,需要唤醒它的后继节点
volatile int waitStatus;
//指向前继节点
volatile Node prev;
//指向后继节点
volatile Node next;
//当前Node封装的线程
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
有关AQS的一些属性就介绍完了,接下来就从去看看ReentrantLock相关的吧。
我们平常用ReentrantLock的时候,一般就是以下的代码模板
Lock lock=new ReentrantLock();
try{
lock.lock();
//业务逻辑
}finally{
lock.unlock();
}
lock()方法用来加锁,获取锁的线程可以继续向下执行业务代码,没有获取锁的线程则需要在此处阻塞。获取锁的线程执行完后,会调用unlock()进行释放锁,并唤醒阻塞的线程。并且ReentrantLock实现了公平和非公平锁,区别就在于释放锁后,公平锁不允许外部线程和已经阻塞的线程进行争抢锁,必须排队;而非公平锁可以让外部线程直接和阻塞住的线程进行争抢。这两种模式可以在代码层面体现出来。
接下来直接看源码。
构造
//默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//根据fair判断创建什么类型的锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
sync是ReentrantLock内部的一个字段,类型是Sync,它继承了AQS,是ReentrantLock的一个静态抽象内部类,所有的操作都是由它来完成的,如果子类有重写,最终会调用到子类实现上去。
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer
公平锁和非公平锁都是继承了Sync
static final class NonfairSync extends Sync
static final class FairSync extends Sync
因为公平锁的实现方法稍微复杂一点,所以我们就拿公平锁分析,非公平锁可以看完本文后自行分析,流程大致相同。
由于AQS采用了模板方法模式,子类按需实现父类的方法,父类最终会调用到子类实现,所以有部分方法会在AQS中,我会在每个方法上都标注好该方法属于哪个类。
lock()流程
#ReentrantLock
public void lock() {
//调用同步器的lock(),此处是公平锁
sync.lock();
}
#FairSync
final void lock() {
acquire(1);
}
#AQS
public final void acquire(int arg) {
//!tryAcquire(arg) true: 表示获取锁失败
//addWaiter(Node.EXCLUSIVE) 将线程包装成Node入队
//acquireQueued() 挂起当前线程
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//设置中断标记位
selfInterrupt();
}
我们将这几个方法拆分看
tryAcquire()
#FairSync
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取AQS的state锁状态
int c = getState();
if (c == 0) {
//说明还未加锁
//hasQueuedPredecessors() 因为是公平锁,所以要检查一下队列中是否在当前线程之前有等待的节点
// true 说明有,当前线程需要入队等待 false 说明没有,尝试获取锁
//compareAndSetState(0, acquires) 加锁,可能有竞争,用cas修改
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//加锁成功就设置独占线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//getExclusiveOwnerThread() 判断独占线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
//是,说明是锁重入
//将锁状态+acquires acquires==1
int nextc = c + acquires;
//锁冲入次数太多,Integer最大值溢出
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//设置state
//为什么这里可以直接设置,而不用cas?
//因为现在是锁重入,当前只有自己这个单线程执行
setState(nextc);
return true;
}
//未获取到锁返回false
return false;
#AQS
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
//此处的判断需要考虑并发情况
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
addWaiter()
#AQS
//mode: Node.EXCLUSIVE 独占模式
private Node addWaiter(Node mode) {
//将当前线程封装成独占模式的Node
Node node = new Node(Thread.currentThread(), mode);
//快速入队
//获取队尾节点
Node pred = tail;
//判断队列是否已经有Node
if (pred != null) {
//将当前node.prev指向队尾
node.prev = pred;
//cas设置队尾为node,这里存在并发
if (compareAndSetTail(pred, node)) {
//成功,将原队尾的next指向node
pred.next = node;
return node;
}
}
//1.空队列
//2.cas入队失败
enq(node);
//入队成功返回当前node
return node;
}
#AQS
private Node enq(final Node node) {
//自旋入队
for (;;) {
//获取队尾
Node t = tail;
//队尾为null,说明没有创建队列
//这里的逻辑是因为第一个获取锁的线程,不用创建节点,直接执行业务逻辑即可
//后面需要阻塞的线程,帮助获取锁的线程创建头节点,因为头节点就代表获取锁的线程
if (t == null) { // Must initialize
//cas设置一个头节点
if (compareAndSetHead(new Node()))
//将tail指向head
tail = head;
//进入下一轮自旋
} else {
//将node.prev指向队尾
node.prev = t;
//cas 修改,在自旋中一定能成功
if (compareAndSetTail(t, node)) {
//cas 成功,将原队尾的next指向node
t.next = node;
return t;
}
}
}
}
acquireQueued()
#AQS
//node:是addWaiter的返回值,也就是当前线程node
final boolean acquireQueued(final Node node, int arg) {
//表示是否需要执行出队逻辑,这是
boolean failed = true;
try {
//当前线程是否被中断
boolean interrupted = false;
//自旋
for (;;) {
//获取当前node的前继节点
final Node p = node.predecessor();
//如果前继节点就是头节点
//并且tryAcquire尝试获取锁成功
if (p == head && tryAcquire(arg)) {
//将自己设置为头节点,因为获取锁成功了,就不需要cas修改
setHead(node);
//将原头节点的next指向null
p.next = null; // help GC
//不需要进行出队逻辑了
failed = false;
//返回当前线程是否被中断过
return interrupted;
}
//如果上面的if失败,那么就需要挂起当前线程了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//被中断过,设置标识位为true
interrupted = true;
}
} finally {
//响应中断的lock(lockInterruptibly())才会执行,本文主要分析普通lock()
if (failed)
cancelAcquire(node);
}
}
#AQS
//pred:前置节点 node:当前节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前置节点的状态
int ws = pred.waitStatus;
//如果前置节点的状态为SIGNAL
if (ws == Node.SIGNAL)
//说明最终唤醒的时候,由前置节点负责唤醒当前node
return true;
//ws>0 表示前置节点的状态为CANCELED
if (ws > 0) {
//所以在这个循环中,向前找到第一个状态为SIGNAL的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//pred就是node前面的第一个状态为SIGNAL的节点,这个操作隐含着将CANCELED的节点出队
pred.next = node;
} else {
//前置节点不为CANCELED,就设置为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
#AQS
private final boolean parkAndCheckInterrupt() {
//在此处park当前线程
LockSupport.park(this);
//在此处被唤醒后返回是否被中断过
return Thread.interrupted();
}
selfInterrupt()
回到#AQS的acquire方法内
#AQS
public final void acquire(int arg) {
//!tryAcquire(arg) true: 表示获取锁失败
//addWaiter(Node.EXCLUSIVE) 将线程包装成Node入队
//acquireQueued() 挂起当前线程,并返回在挂起期间,线程是否被中断过
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//执行到这说明被中断过
//设置中断标记位
selfInterrupt();
}
#AQS
static void selfInterrupt() {
//设置中断
Thread.currentThread().interrupt();
}
到这里lock()的流程就已经结束了,我们来总结一下。
1.lock()内会调用同步器的acquire(1)方法;
2.acquire(1)内首先调用自定义同步器的tryAcquire()尝试去获取锁,成功则返回;
3.tryAcquire()失败,addWaiter()会将当前线程包装成独占模式的Node加入到队列尾部;
4.acquireQueued()会让当前线程的Node向前找到一个负责唤醒自己的Node,随后park。直到被其他线程unpark后,会尝试获取锁资源。如果在等待过程中被中断过,则返回true,否则返回false;
5.lock()是不响应中断的,所以最后返回到acquire()方法内,如果在等待中被中断过,拿到锁资源后会自己中断一次。
接下来分析unlock()
unlock()流程
#ReentrantLock
public void unlock() {
sync.release(1);
}
#AQS
public final boolean release(int arg) {
//尝试释放锁
if (tryRelease(arg)) {
//释放成功,获取队列头节点
Node h = head;
//h!=null 说明队列已经被创建,需要进行出队和唤醒后继节点的逻辑
//h.waitStatus != 0 说明一定有后继节点,因为创建节点waitStatus是默认值0, !=0说明是被后继节点修改的状态
if (h != null && h.waitStatus != 0)
//唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease()
#Sync
protected final boolean tryRelease(int releases) {
//获取state值,并减去releases,表示释放一次锁
int c = getState() - releases;
//如果当前线程不是独占线程
if (Thread.currentThread() != getExclusiveOwnerThread())
//说明当前线程还没加过锁就来释放锁,抛异常
throw new IllegalMonitorStateException();
boolean free = false;
//当锁释放完成
if (c == 0) {
free = true;
//清除独占线程
setExclusiveOwnerThread(null);
}
//设置state值
setState(c);
return free;
}
unparkSuccessor()
private void unparkSuccessor(Node node) {
//获取node的状态值,从解锁过程来看,node是头节点
int ws = node.waitStatus;
//SIGNAL==-1 说明要负责唤醒后继节点
if (ws < 0)
//cas修改当前节点的状态值,因为当前节点已经完成了负责唤醒后继节点的任务
compareAndSetWaitStatus(node, ws, 0);
//找到头节点的后继节点
Node s = node.next;
//s==null 说明队列只有node一个节点
//s.waitStatus > 0 前提条件是s!=null,说明s节点是CANCELLED状态
if (s == null || s.waitStatus > 0) {
s = null;
//从后向前查找离当前node最近的一个可唤醒的节点,并赋值给s
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//unpark s节点的线程
//线程会在park点,也就是parkAndCheckInterrupt()里被唤醒,然后开始新的一轮争抢锁
LockSupport.unpark(s.thread);
}
到这里unlock()的流程就分析完了,我们来总结一下
1.unlock()会调用到#AQS的release(1)方法;
2.release(1)会调用tryRelease()尝试释放锁,在释放前还会检查当前线程是否加过锁并释放资源,否则抛异常;
3.unparkSuccesso()负责unpark唤醒后继节点。
以上就是ReentrantLock公平锁的lock()和unlock()的源码分析,非公平锁流程基本一致,只不过不需要在加锁前进行判断队列中是否还有需要唤醒的节点这一个过程,大家可以自行查看一下,顺便将AQS的独占模式弄清楚,方便后期看其他的源码。本文还有一个响应中断的加锁方法没有分析,后期如果弄清楚了,会在后面进行更新。