一、AbstractQueuedSynchronizer类分析
在学习ReentrantLock前,我们先了解一下AQS。AQS (AbstractQuenedSynchronizer)是抽象的队列式同步器。是多线程访问共享资源的同步器基础框架。
AQS是基于CLH队列锁实现的,用volatile修饰共享变量state,采用CAS(compareAndSwap)来修改state。state修改成功,则获得锁;修改失败,则进入CLH队列,等待被唤醒。
CLH(Craig,Landin,and Hagersten)队列: CLH队列是一个虚拟双向队列,即不存在队列实例,仅存在节点之间关联关系。
AQS的特点:
- 可重入性
- 公平/非公平性
- 阻塞队列等待
- 共享/独占
- 允许中断
AQS定义了两种队列
- 同步等待队列:
- 条件等待队列:
二、ReentrantLock简介
ReentrantLock是一种基于AQS框架的应用实现,一种支持公平或非公平、可中断、可重入的锁。它的功能类似于synchronized是一种互斥锁,可以保证线程安全。
1.类的继承关系
ReentrantLock实现Lock接口,Lock接口中常用的方法有lock()、unlock()以及newCondition(),
newCondition方法表示生成一个条件。
public class ReentrantLock implements Lock, java.io.Serializable
Lock接口中的主要方法
2.类的内部结构
ReentrantLock有三个内部类,分别是Sync、NonfairSync、FairSync;两个构造方法
类结构图
说明:FairSync类和NonfairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。
3.类的构造函数
3.1无参构造函数ReentrantLock()
默认采用非公平策略
public ReentrantLock() {
//默认非公平策略
sync = new NonfairSync();
}
3.2有参构造函数ReentrantLock(boolean fair)
fair: true表示公平策略,false:表示非公平策略
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
三、ReentrantLock源码分析
public class ReentrantLockTest1 {
private volatile static int count = 0;
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
#lock.lock实际调用的方法
public void lock() {
sync.lock();
}
Sync类的lock()方法是抽象方法,由子类FairSync和NonfairSync具体实现,我们先分析默认非公平锁NonfairSync的加锁过程
1. NonfairSync.lock
final void lock() {
//cas抢占锁资源
if (compareAndSetState(0, 1))
//cas成功,设置当前线程为锁的占用者
setExclusiveOwnerThread(Thread.currentThread());
else
//cas失败,调用acquire(1)竞争锁
acquire(1);
}
CAS原理
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
compareAndSetState通过乐观锁的作比较并替换,表示当内存中的state值和预期expect值相等,则将state修改为update值。更新成功返回ture,否则返回false。
state是AQS类中的一个属性,含义如下:
- state=0,表示无锁状态
- state>0,表示有线程获得了锁,此时state=1。由于ReentrantLock可重入,所以当同一个线程获取多次锁时,state会递增,比如重入5次,state=5。而在释放锁的时候,需要释放5次直到state=0,其他线程才资格获得锁。
2. AQS.acquire
acquire(int args)是AQS类的方法
public final void acquire(int arg) {
//1.尝试获取锁,获取成功返回true,获取失败,返回false
//2.tryAcquire失败,通过addWaiter()方法将当前线程封装成Node添加到AQS队列尾部
//3.acquireQueued方法将Node作为参数,通过自旋去尝试获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1)NonfairSync.tryAcquire
NonfairSync重写 AQS 类中的 tryAcquire(),AQS 中 tryAcquire方法的定义,并没有实现,而是抛出异常。该方法的作用是尝试获取锁,获取成功,返回true,否则返回false。
#ASQS类中的tryAcquire方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
#NonfairSync类中的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
NonfairSync类中的tryAcquire()方法调用了父类的nonfairTryAcquire()方法,个人觉得nonfairTryAcquire方法可以写在NonfairSync类中。
2)Sync.nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取同步锁状态值state
int c = getState();
if (c == 0) {
//无锁,使用CAS抢占所资源
if (compareAndSetState(0, acquires)) {
//cas成功,设置当前线程为锁的占有者
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前线程是否为锁的占有者
else if (current == getExclusiveOwnerThread()) {
//增加重入次数
int nextc = c + acquires;
//超过int类型最大值
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
可以看到,nonfairTryAcquire()方法的加锁有以下几步:
a.获取当先指向的线程
b.获取state值,判读是否有锁,无锁,通过CAS抢占锁资源
c.有锁,判断当前线程是否为占有锁的线程,直接增加重入次数
d.获取锁成功,返回true,否则返回false
3. AQS.addWriter
当tryAcquire()方法获取锁失败后,通过addWaiter()方法将当前线程封装成Node添加到AQS队列尾部。入参mode表示当前节点的状态,传递的参数是Node.EXCLUSIVE,表示独占状态。这是AQS的独占锁功能。
private Node addWaiter(Node mode) {
//将当前线程封装成Node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//tail为AQS队列尾节点,默认为null,将队列尾节点位置给临时节点pred
Node pred = tail;
//如果队列尾部节点tail不为空,说明队列中存在节点
if (pred != null) {
//将当前线程节点node的prev前继节点指向 尾节点tail
node.prev = pred;
//通过cas,把当前线程节点node设置为队列的尾节点,这样就加入到AQS队列尾部了
if (compareAndSetTail(pred, node)) {
//设置成功后,把原来尾节点的next后继节点指向node
pred.next = node;
return node;
}
}
//队列尾节点为空,把node添加到AQS队列
enq(node);
return node;
}
可以看到,addWriter()主要分为以下几步:
a.将当前线程封装成Node
b.判断AQS队列的尾节点tail是否为空,如果不为空,通过CAS把当前线程节点node设置为队列尾节点,加入到AQS队列尾部
c.如果AQS队列的尾节点tail为空,或者CAS失败,调用enq()方法将node节点添加到AQS队列
1)enq
enq()方法就是通过自旋操作把当前节点加入到队列中。
private Node enq(final Node node) {
for (;;) {
//获取尾节点
Node t = tail;
//尾节点为空,说明队列为空
if (t == null) { // Must initialize
//队列为空,通过CAS设置一个新创建的空节点为头节点,初始化队列
if (compareAndSetHead(new Node()))
//尾节点也指向空节点
tail = head;
} else {
//尾节点不为空,将当前节点的prev前继节点指向尾节点
node.prev = t;
//通过CAS设置当前节点为尾节点
if (compareAndSetTail(t, node)) {
//设置成功后,把原来尾节点的next指针指向当前节点
t.next = node;
return t;
}
}
}
}
2)图解分析
4. AQS.acquireQueued
通过前一步addWriter()将线程添加到队列后,会把入队的Node作为参数,传递给acquireQueued()作为入参去竞争锁。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的prev节点
final Node p = node.predecessor();
//如果prev节点是头节点,说明有资格竞争锁
if (p == head && tryAcquire(arg)) {
//获取锁成功,将当前节点设置为头节点
setHead(node);
//把原来的head节点从队列中移出
p.next = null; // help GC
failed = false;
return interrupted;
}
//获取锁失败后,根据waitStatus判断是否要挂起线程,挂起后,判断线程是否要中断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
//最后,通过 cancelAcquire 取消获得锁的操作
cancelAcquire(node);
}
}
1)获取当前节点的 prev 节点
2)如果 prev 节点为 head 节点,那么它就有资格去争抢锁,调用 tryAcquire 抢占锁
3)抢占锁成功以后,把获得锁的节点设置为 head,并且移除原来的初始化 head节点
4)如果获得锁失败,则根据 waitStatus 决定是否需要挂起线程
5)最后,通过 cancelAcquire 取消获得锁的操作
shouldParkAfterFailedAcquire()方法根据 waitStatus 决定是否需要挂起线程
#在同步队列中等待的线程等待超时或被中断
static final int CANCELLED = 1;
#只要前置节点释放锁,就会通知标识为 SIGNAL 状态的后续节点的线程
static final int SIGNAL = -1;
#和 Condition 有关系
static final int CONDITION = -2;
#共享模式下,PROPAGATE 状态的线程处于可运行状态
static final int PROPAGATE = -3;
# 0:初始状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前置节点的waitStatus
int ws = pred.waitStatus;
//如果前置节点为 SIGNAL,意味着只需要等待其他前置节点的线程被释放
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
//返回 true,意味着可以被挂起
return true;
if (ws > 0) {
//ws>0,表示prev节点取消了排队,直接移除这个节点
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
//相当于pred=pred.prev,node.prev=pred
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//从双向列表中移除 CANCELLED 的节点
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//使用CAS设置prev节点的waitStatus为Node.SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt 挂起当前线程变成 WATING 状态
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
四、锁的释放流程
1. unlock
public void unlock() {
sync.release(1);
}
在unlock()方法中会调用release(1)方法
public final boolean release(int arg) {
//释放锁成功
if (tryRelease(arg)) {
//获取队列中的头节点
Node h = head;
//如果头节点不为空,并且waitStatus != 0
if (h != null && h.waitStatus != 0)
//调用unparkSuccessor(h)唤醒后续节点
unparkSuccessor(h);
return true;
}
return false;
}
2. tryRelease
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;
//state=0,表示没有线程占用锁,将 Owner 线程设置为空
setExclusiveOwnerThread(null);
}
//否则,修改state状态值
setState(c);
return free;
}
3. unparkSuccessor
private void unparkSuccessor(Node node) {
//获取head节点的waitStatus状态
int ws = node.waitStatus;
if (ws < 0)
// 设置 head 节点状态为 0
compareAndSetWaitStatus(node, ws, 0);
//获取head节点的下一个节点
Node s = node.next;
//如果下一个节点为 null 或者 status>0 表示节点为cancelled 状态
if (s == null || s.waitStatus > 0) {
s = null;
//通过从尾部节点开始扫描,找到距离 head 最近的一个waitStatus<=0 的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//next 节点不为空,直接唤醒这个线程即可
LockSupport.unpark(s.thread);
}
五、原本挂起的线程继续执行
通过 ReentrantLock.unlock,原本挂起的线程被唤醒以后继续执行,原来被挂起的线程是在 acquireQueued 方法中,所以被唤醒以后继续从这个方法开始执行。
六、公平锁和非公平锁的区别
锁的公平性是相对于获取锁的顺序而言的,如果是一个公平锁,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是 FIFO。 在上面分析的例子来说,只要CAS 设置同步状态成功,则表示当前线程获取了锁,而公平锁则不一样,差异点有两个:
1. FairSync.tryAcquire
final void lock() {
acquire(1);
}
非公平锁在获取锁的时候,会先通过 CAS 进行抢占,而公平锁则不会。
2.FairSync .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;
}
}
这个方法与 nonfairTryAcquire(int acquires)比较,不同的地方在于判断条件多了hasQueuedPredecessors()方法,也就是加入了[同步队列中当前节点是否有前驱节点]的判断,如果该方法返回 true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。