1、 ReentrantLock的结构:
ReentrantLock的底层是借助AbstractQueuedSynchronizer实现,所以其数据结构依附于AbstractQueuedSynchronizer的数据结构。
ReentrantLock实现Lock接口,Sync与ReentrantLock是组合关系,且FairSync(公平锁)、NonfairySync(非公平锁)是Sync的子类。Sync继承AQS(AbstractQueuedSynchronizer)。
AQS(AbstractQueuedSynchronizer): 为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用 getState()、setState(int) 和 compareAndSetState(int, int) 方法来操作以原子方式更新的 int 值。
CLH : AQS中“等待锁”的线程队列。在多线程环境中我们为了保护资源的安全性常使用锁将其保护起来,同一时刻只能有一个线程能够访问,其余线程则需要等待,CLH就是管理这些等待锁的队列。
CAS(compare and swap):比较并交换函数,它是原子操作函数,也就是说所有通过CAS操作的数据都是以原子方式进行的。
2、源码分析
2.1 类的继承
public class ReentrantLock implements Lock, java.io.Serializable
ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。
2.2 类的内部类
ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类,如上图:
2.2.1 Sync:
abstract static class Sync extends AbstractQueuedSynchronizer {
// 序列号
private static final long serialVersionUID = -5179523762034025860L;
// 获取锁
abstract void lock();
// 非公平方式获取
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 表示没有线程正在竞争该锁
if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用
// 设置当前线程独占
setExclusiveOwnerThread(current);
return true; // 成功
}
}
else if (current == getExclusiveOwnerThread()) { // 当前线程拥有该锁
int nextc = c + acquires; // 增加重入次数
if (nextc < 0) // overflow
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;
if (c == 0) {
free = true;
// 已经释放,清空独占
setExclusiveOwnerThread(null);
}
// 设置标识
setState(c);
return free;
}
// 判断资源是否被当前线程占有
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
// 新生一个条件
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
// 返回资源的占用线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 返回状态
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 资源是否被占用
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
// 自定义反序列化逻辑
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
Sync类存在如下方法和作用:
2.2.2 NonfairSync类:继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法
// 非公平锁
static final class NonfairSync extends Sync {
// 版本号
private static final long serialVersionUID = 7316153563782823691L;
// 获得锁
final void lock() {
if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
// 把当前线程设置独占了锁
setExclusiveOwnerThread(Thread.currentThread());
else // 锁已经被占用,或者set失败
// 以独占模式获取对象,忽略中断
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
2.2.3 FairSyn类:继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法
// 公平锁
static final class FairSync extends Sync {
// 版本序列化
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 以独占模式获取对象,忽略中断
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 尝试公平获取锁
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 状态为0
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
// 设置当前线程独占
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
// 下一个状态
int nextc = c + acquires;
if (nextc < 0) // 超过了int的表示范围
throw new Error("Maximum lock count exceeded");
// 设置状态
setState(nextc);
return true;
}
return false;
}
}
跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。
2.3 类的属性
public class ReentrantLock implements Lock, java.io.Serializable {
// 序列号
private static final long serialVersionUID = 7373984872572414699L;
// 同步队列
private final Sync sync;
}
2.4 类的构造函数
2.4.1 ReentrantLock()型构造函数:
public ReentrantLock() {
// 默认非公平策略
sync = new NonfairSync();
}
可以看到默认是采用的非公平策略获取锁。
2.4.2 ReentrantLock(boolean)型构造函数:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略。
2.5 核心函数分析
2.5.1 获取非公平锁
final void lock() {
if (compareAndSetState(0, 1)) //先去获取锁,获取失败,在执行下一步
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
1、首先通过CAS更新AQS中的状态变量来获得锁(第一次获得锁),如果获取成功则把当前线程设置为独占锁
2、如果失败,再去执行acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1、先执行的tryAcquire方法,尝试获得锁。
2、如果获取失败则进入addWaiter方法,构造同步节点,将该节点添加到同步队列尾部,并返回此节点,进入acquireQueued方法。
3、acquireQueued方法,这个新节点是循环的方式获取同步状态,如果获取不到则阻塞节点中的线程,阻塞后的节点等待前驱节点来唤醒。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); //可以看出这里直接调用的是nonfairTryAcquire方法
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //获取到当前的线程
int c = getState(); //获取锁的状态
if (c == 0) { //目前没有人在占有锁
if (compareAndSetState(0, acquires)) { //直接尝试把当前只设置成1,如果成功,把owner设置自己,并且推出
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; //这里就是重入锁的概念,如果还是自己,则进行加1操作,因为释放和获取一定要是对等的
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;//当前锁被其他线程占用,退出。
}
1、ryAcquire调用nonfairTryAcquire方法来第二次尝试获得锁
2、如果状态变量为0,则进行CAS尝试更新状态来获得锁,并把该线程设置成独占锁,并返回真。
3、如果状态变量不为0,则判断当前线程是否为独占锁,如果是,则当前状态+ 1(可重入锁),表示获取锁成功,更新状态值,并返回真。这里更新状态变量,不需要CAS更新,因为,当前线程已经获得锁。
说明:以上的源码大致和FairSync相同的,唯一不同的地方,就是当c==0的时候,公平锁需要去hasQueuedPredecessors,去判断一下是否有前驱节点,在判断是否要去设置,获取锁。
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;
}
将构造的同步节点加入到同步队列中
1、使用链表的方式把该节点节点添加到队列尾部,如果尾的前驱节点不为空(队列不为空),则进行CAS添加到队列尾部。
2、如果更新失败(存在并发竞争更新),则进入ENQ方法进行添加
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
该方法使用CAS自旋的方式来保证向队列中添加节点
1、如果队列为空,则把当前节点设置成头节点
2、如果队列不为空,则向队列尾部添加节点
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;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在acquireQueued方法中,当前线程在死循环中尝试获取同步状态:
1、如果当前节点的前驱节点头节点才能尝试获得锁,如果获得成功,则把当前线程设置成头结点,把之前的头结点从队列中移除,等待垃圾回收(没有对象引用)
2、如果获取锁失败则进入shouldParkAfterFailedAcquire方法中检测当前节点是否可以被安全的挂起(阻塞),如果可以安全挂起则进入parkAndCheckInterrupt方法,把当前线程挂起,并检查刚线程是否执行了中断方法。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
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 {
/*
* 尝试将当前节点的前驱节点的等待状态设为SIGNAL
* 1/这为什么用CAS,现在已经入队成功了,前驱节点就是pred,除了node外应该没有别的线程在操作这个节点了,那为什么还要用CAS?而不直接赋值呢?
* (解释:因为pred可以自己将自己的状态改为cancel,也就是pred的状态可能同时会有两条线程(pred和node)去操作)
* 2/既然前驱节点已经设为SIGNAL了,为什么最后还要返回false
* (因为CAS可能会失败,这里不管失败与否,都返回false,下一次执行该方法的之后,pred的等待状态就是SIGNAL了)
* (网上摘抄的,解释的很明白)
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
1、首先获取前驱节点的等待状态WS
2、如果WS为SIGNAL则表示可以被前驱节点唤醒,当前线程就可以挂起,等待前驱节点唤醒,返回真(可以挂起)
3、如果WS> 0说明,前驱节点取消了,并循环查找此前驱节点之前所有连续取消的节点。并返回假(不能挂起)。
4、尝试将当前节点的前驱节点的等待状态设为信号
waitStatus状态值:
取消 1 等待超时或者中断,需要从同步队列中取消
信号 -1 后继节点出于等待状态,当前节点释放锁后将会唤醒后继节点
条件 -2 节点在等待队列中,节点线程等待在条件上,其它线程对条件调用信号()方法后,该节点将会从等待同步队列中移到同步队列中,然后等待获取锁。
传播 -3 表示下一次共享式同步状态获取将会无条件地传播下去
初始 0 初始状态
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
把当前线程挂起,并检查刚线程是否执行了中断方法,并返回真,假。
2.5.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) {
int c = getState() - releases; //获取当前的锁的状态并且减1,因为要释放锁
if (Thread.currentThread() != getExclusiveOwnerThread()) //如果当前自己不是锁的持有者,只有自己才能释放锁
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //释放成功
free = true;
setExclusiveOwnerThread(null);
}
setState(c); //重新设置成状态
return free;
释放锁成功后,获取头节点,接着唤醒后继节点,调用unparkSuccessor方法:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus; //获取头结点的等待状态
if (ws < 0) //把该状态设置成0
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; //找到后继节点,唤醒后继节点
if (s == null || s.waitStatus > 0) { //很不巧,后继节点,节点为null,或者被取消
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中维护的两个队列:
1、Condition队列,表示等待的队列,其waitStatus=Node.Condition,由firstWaiter和lastWaiter两个属性操控.
2、Sync队列,表示可以竞争锁的队列,这个跟AQS一致,waitStatus=0;
3、await()方法就是把当前线程创建一个Node加入Condition队列,接着就一致循环查其在不在Sync队列,如果当前节点在Sync队列里了,就可以竞争锁,恢复运行了.
4、signal()方法就是把某个节点的nextWaiter设为null,再把其从Condition队列转到Sync队列.