1.示例代码
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
2.ReentrantLock构造方法
private final Sync sync;
//空构造的情况创建一个非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//传boolean值,true的情况创建一个公平锁,false创建一个非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
3.Sync,FairSync,AbstractQueuedSynchronizer继承关系
AbstractQueuedSynchronizer提供了通用方法,但其tryAcquire方法需要子类FairSync自己实现
4.Node节点
AbstractQueuedSynchronized内部维护了一个双向链表,通过waitStatus来控制线程执行状态
阻塞队列不包含 head 节点,这里要先有一个概念,为什么呢?因为首节点是一个虚节点的概念,不存储数据,从addWaiter方法中的enq方法内可以看出来。在添加一个节点进队列的时候,第一次添加的时候会构建一个空节点。
private Node enq(final Node node) {
for (;;) {
//获取当前尾节点
Node t = tail;
//如果当前尾节点为空
if (t == null) {
//构造一个空节点并赋值给head节点
if (compareAndSetHead(new Node()))
//将尾节点指向当前空的首节点
tail = head;
} else {
//循环第二次进来,尾节点一点不为空
//设置当前节点的前置节点为之前的尾节点
node.prev = t;
//设置当前节点为尾节点
if (compareAndSetTail(t, node)) {
//设置初始化为空的尾节点的后继节点为当前节点
t.next = node;
//返回空节点
return t;
}
}
}
}
static final class Node {
//代表节点当前在共享模式下
static final Node SHARED = new Node();
//代表当前节点在独占模式下
static final Node EXCLUSIVE = null;
//当前节点线程取消争抢锁
static final int CANCELLED = 1;
//当前node节点的后继节点的线程需要被唤醒
static final int SIGNAL = -1;
//codition节点
static final int CONDITION = -2;
//
static final int PROPAGATE = -3;
//取值为上面的1,-1,-2,-3或者默认值0
volatile int waitStatus;
//前驱节点
volatile Node prev;
//后继节点
volatile Node next;
//当前node节点对应的线程
volatile Thread thread;
//
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
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;
}
}
5.state属性
state值代表当前线程是否获取锁
//AbstractQueuedSynchronizer的核心属性,大于0说明线程持有锁,每重入一次该值+1
//所谓的重入可以理解为lock.lock()调用多次
private volatile int state;
//父类AbstractQueuedSynchronizer的方法
//通过UNSAFE类设置内存地址中state的属性值,当state>=1的时候,说明当前线程持有锁
//线程每进行一次重入,state值会+1
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
6.从ReentrantLock公平锁分析AbstractQueuedSynchronized
加锁过程方法调用图,方便分析代码调用过程
6.1.加锁过程分析
6.1.1.AbstractQueuedSynchronized.acquire方法分析
//加锁核心方法入口
public final void acquire(int arg) {
//条件1:FairSync.tryAcquire尝试获取锁,获取成功返回true,失败返回false
//条件2:addWaiter将当前线程封装成Node加入到双向队列中
//acquireQueued为核心方法,包含阻塞当前线程,清除队列中CANCELED状态线程
//返回true表示
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();//设置当前线程的中断状态,这里涉及interrupt()和interrupted()方法的概念
}
interrupt()只设置线程的中断状态为true,并不能中断线程;
interrupted()测试当前线程是否已经被中断,并清除线程的中断状态;
6.1.2.FairSync.tryAcquire方法分析
aqs将tryAcquire交给子类去实现,这里示例为FairSync
//尝试获取锁
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前线程状态
int c = getState();
//c==0说明当前线程还未获取锁
if (c == 0) {
//条件1:判断当前队列中是否有其他线程在等待,没有则返回false
//条件2:条件1中队列中没有其他线程在等待,尝试修改state值,修改成功则代表加锁成功
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//设置当前线程为独占线程
setExclusiveOwnerThread(current);
return true;
}
}
//c>0说当前线程已经获取锁,判断当前线程和独占线程是否相等
else if (current == getExclusiveOwnerThread()) {
//将state的值递增
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//更新state的值
setState(nextc);
return true;
}
return false;
}
6.1.3.AbstractQueuedSynchronized.hasQueuedPredecessors方法分析
面有一个hasQueuedPredecessors方法,这是和非公平锁的区别,公平锁这里多了这个方法。用于判断队列中是否已经有其他线程在等待,如果没有,则返回false。因为前面也已经说过head节点不存储数据,只是一个虚节点,所以判断队列中是否有处于等待的节点有以下判断:
1.h != t,如果head和tail相同,也就不用判断了,肯定没有处于等待的节点。
2.(s = h.next) == null说明只有首节点,肯定没有处于等待的节点。
3.s.thread != Thread.currentThread(),首节点的next节点的线程不和当前线程一样。如果相同,说明当前首节点只有一个next节点,也就是只有一个线程竞争锁资源,可以直接通过CAS竞争锁资源。
这里有个细节,tail的声明在(读法网https://www.dufawa.com/tags-80-0.html)head之前,因为根据tail你一定可以获取head,但是反过来有head就不一定有tail了。因为head肯定是在tail之前初始化的。这样在多线程竞争情况下,如果head先声明,tail后声明,就会出现head初始化了但tail还未初始化的过程,是的h!=t等式成立。
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
6.1.4.AbstractQueuedSynchronized.addWaiter方法分析
//将当前线程封装成Node节点并返回,指定为独占模式,则节点的nextWaiter为NULL
//如果当前节点存在尾节点,则将当前节点加入到队列中,并设置当前节点为尾节点
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
//将节点加入到队尾当中
Node pred = tail;
//如果尾节点不为空
if (pred != null) {
//设置当前节点的前驱节点为尾节点
node.prev = pred;
//通过cas方式设置当前节点为尾节点
if (compareAndSetTail(pred, node)) {
//之前尾节点的后续节点为当前节点
pred.next = node;
return node;
}
}
//两种情况走到这里:1.尾节点为空,则队列为空;2.通过CAS设置尾节点失败,存在竞争的时候可能出现
//自旋方式设置当前节点为尾节点,如果尾节点为空,则初始化一个空节点作为首节点,并将当前节点和空的首
//节点构成双向队列,返回该空节点
enq(node);
return node;
}
6.1.5.AbstractQueuedSynchronized.enq方法分析
自旋方式设置当前节点为尾节点,如果尾节点为空,则初始化一个空节点作为首节点,并将当前节点和空的首节点构成双向队列,返回该空节点
private Node enq(final Node node) {
for (;;) {
//获取当前尾节点
Node t = tail;
//如果当前尾节点为空
if (t == null) {
//构造一个空节点并赋值给head节点
if (compareAndSetHead(new Node()))
//将尾节点指向当前空的首节点
tail = head;
} else {
//循环第二次进来,尾节点一点不为空
//设置当前节点的前置节点为之前的尾节点
node.prev = t;
//设置当前节点为尾节点
if (compareAndSetTail(t, node)) {
//设置初始化为空的尾节点的后继节点为当前节点
t.next = node;
//返回空节点
return t;
}
}
}
}
6.1.6.AbstractQueuedSynchronized.acquireQueued方法分析
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);
//之前头节点的后续节点设置为空,帮助gc
p.next = null; // help GC
//失败标识设置为false
failed = false;
return interrupted;
}
//如果节点的前驱节点不是头节点或者尝试加锁失败
//shouldParkAfterFailedAcquire判断当前节点线程是否需要阻塞
//当能阻塞当前线程时,调用parkAndCheckInterrupt方法阻塞线程
//如果被阻塞,当前自旋操作也走不下去了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//如果阻塞过程发生异常或其他原因导致失败,将当前线程状态设置为CANCELED,同时从队列中剔除
if (failed)
cancelAcquire(node);
}
}
6.1.7.AbstractQueuedSynchronized.shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取当前节点前驱节点的状态
int ws = pred.waitStatus;
//如果前驱节点状态为SIGNAL,当前节点线程可以直接阻塞
if (ws == Node.SIGNAL)
return true;
//如果前驱节点的状态为CANCELLED,从当前节点向前循环获取第一个waitStatus不为CANCELLED的节点,
//并将找到的前驱节点和当前节点构成双向队列,剔除CANCELLED状态的节点
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//如果ws=0,设置当前节点的前驱节点的状态为SIGNAL
//通过cas设置前驱节点的状态为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
6.1.8.AbstractQueuedSynchronized.parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
//阻塞当前线程,当前线程已被中断
LockSupport.park(this);
//清除当前线程的中断状态并测试线程是否已被中断,这里返回true表示线程已经被中断
//虽然这里清除了线程的中断状态,但acquire方法里面调用了selfInterrupt()设置了线程的中断状态
return Thread.interrupted();
}
6.1.9.AbstractQueuedSynchronized.cancelAcquire方法
取消加锁操作,将当前Node从队列中移除,构建新的队列,移除构建新的队列过程中遍历到的CANCELED状态的节点,根据当前节点所处位置分析是否需要唤醒当前节点的后续节点。
这段代码首先干了这几件事:
首先将当前节点的线程设置为空,状态设置为CANCELED,从后向前找到当前节点第一个状态不为CANCELED的前驱节点,并把当前节点的前驱节点设置为找到的这个节点。
再来就是构建新的双向队列,根据当前节点所处的位置有三种情况:
(1).当前节点是尾节点。这种情况我们要做什么呢?前面我们已经从后向前获取到了当前节点的前驱节点中第一个不为CANCELED状态的节点,那么是否就需要将所有的CANCELED状态的节点和当前节点剔除呢?没错,这里面就做了这件事。把找到的前驱节点设置为尾节点,把找到的前驱节点的后续节点设置为空,这样就形成了新的双向队列。
(2).当前节点不是尾节点,也不是head的后继节点,对应的就是这个条件判断pred != head。这种情况节点就处于中间了。
类似结构:head<->node1<->node2<->...<->pred(searchNode)<->...(canceled状态节点)<->currentNode<->next<->...<->tail,...(canceled状态节点)<->currentNode这之间的节点都需要剔除,然后将pred(searchNode)<->next构成新的链表。
那么这里是否还需要将找到的pred节点的状态设置为SIGNAL呢,代表后续节点需要被唤醒。
(3).当前节点是head节点的后继节点,需要唤醒当前节点的后继节点,具体方法在unparkSuccessor中
private void cancelAcquire(Node node) {
if (node == null)
return;
//将当前节点线程设置为空
node.thread = null;
//获取当前节点的前驱节点
Node pred = node.prev;
//从后向前循环,找到当前节点的前驱节点中第一个不为CANCELED状态的节点
//并设置当前节点的前驱节点为找到的节点
while (pred.waitStatus > 0)
//node.prev = pred->设置当前节点的前驱节点
//pred = pred.prev
node.prev = pred = pred.prev;
//获取找到的前驱节点的后续节点
//可能是CANCELLED状态的节点,也可能是当前节点自己
Node predNext = pred.next;
//设置当前节点的状态为CANCELLED
node.waitStatus = Node.CANCELLED;
//(1)
//1.如果当前节点是尾节点
//则设置找到的当前节点不为CANCELLED状态的节点为尾节点,为CANCELLED状态的节点可以从队列中剔除了
if (node == tail && compareAndSetTail(node, pred)) {
//同时设置找到的当前节点不为CANCELLED状态的前驱节点的尾节点为空,剔除队列中CANCELLED状态的节点
compareAndSetNext(pred, predNext, null);
} else {
//(2)
int ws;
//2.当前节点不是head的next节点也不是尾节点
//条件1:当前节点不是head的next节点也不是尾节点
//条件2.1:(ws = pred.waitStatus) == Node.SIGNAL,说明当前节点找到的前驱节点状态是SIGNAL
//如果不是SIGNAL,也可能为0
//条件2.2:ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)
//如果前驱节点的状态<=0,则设置找到的前驱节点的状态为SIGNAL,表示需要唤醒后继节点
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
//获取当前节点的后续节点
Node next = node.next;
//如果当前节点后续节点不为空且状态<=0
if (next != null && next.waitStatus <= 0)
//设置当前节点找到的前驱节点的后继节点为当前节点的next节点,剔除CANCELLED状态的节点
compareAndSetNext(pred, predNext, next);
} else {
//(3)
//当前节点是head的next节点,需要唤醒当前节点的后继节点,具体方法在unparkSuccessor中
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
6.1.10.AbstractQueuedSynchronized.unparkSuccessor方法
private void unparkSuccessor(Node node) {
//获取当前节点状态
int ws = node.waitStatus;
//如果状态是<0,则设置当前节点状态为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//如果ws > 0
//获取当前节点的后续节点
Node s = node.next;
//如果后续节点空或者状态为CANCELED
if (s == null || s.waitStatus > 0) {
s = null;
//从后向前找到最靠近当前节点的状态<1的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//如果找到了这个节点,解除线程的阻塞
if (s != null)
LockSupport.unpark(s.thread);
}
6.2.释放锁过程分析
lock.unlock();
这段代码主要调了AbstractQueuedSynchronized的release方法。
6.2.1.分析AbstractQueuedSynchronized的release方法
public final boolean release(int arg) {
//尝试释放锁,当state=0的时候返回true
if (tryRelease(arg)) {
Node h = head;
//当首节点不为空且首节点状态不为初始化的状态的情况,唤醒后续节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//递减c的值,该方法也是aqs交给子类实现的,这里的具体实现是在Sync当中
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//当c的值递减为0的时候
if (c == 0) {
free = true;
//设置独占线程为空
setExclusiveOwnerThread(null);
}
//设置state的值为0
setState(c);
return free;
}