勿忘初心2018-12-19 10:05此文已由作者赵计刚授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
3.2.2、AbstractOwnableSynchronizer:属性+setExclusiveOwnerThread(Thread t)
3.2.3、AbstractQueuedSynchronizer:属性+acquire(int arg)
在介绍上边这个方法之前,先要说一下AbstractQueuedSynchronizer的一个内部类Node的整体构造,源代码如下:
/**
* 同步等待队列(双向链表)中的节点
*/
static final class Node {
/** 线程被取消了 */
static final int CANCELLED = 1;
/**
* 如果前驱节点的等待状态是SIGNAL,表示当前节点将来可以被唤醒,那么当前节点就可以安全的挂起了
* 否则,当前节点不能挂起
*/
static final int SIGNAL = -1;
/**线程正在等待条件*/
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** 一个标记:用于表明该节点正在独占锁模式下进行等待 */
static final Node EXCLUSIVE = null;
//值就是前四个int(CANCELLED/SIGNAL/CONDITION/PROPAGATE),再加一个0
volatile int waitStatus;
/**前驱节点*/
volatile Node prev;
/**后继节点*/
volatile Node next;
/**节点中的线程*/
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special value SHARED.
* Because condition queues are accessed only when holding in exclusive
* mode, we just need a simple linked queue to hold nodes while they are
* waiting on conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive, we save a
* field by using special value to indicate shared mode.
*/
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) { // 用于addWaiter中
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
注意:这里我给出了Node类的完整版,其中部分属性与方法是在共享锁的模式下使用的,而我们这里的ReentrantLock是一个独占锁,只需关注其中的与独占锁相关的部分就好(具体有注释)
3.3、AbstractQueuedSynchronizer:acquire(int arg)方法中使用到的两个方法
3.3.1、NonfairSync:tryAcquire(int acquires)
/**
* 试着请求成功
*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
Syn:
/**
* 非公平锁中被tryAcquire调用
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();//获取当前线程
int c = getState();//获取锁数量
if (c == 0) {//如果锁数量为0,证明该独占锁已被释放,当下没有线程在使用
if (compareAndSetState(0, acquires)) {//继续通过CAS将state由0变为1,注意这里传入的acquires为1
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;
}
注意:这个方法就完成了"简化版的步骤"中的"A/B/B1"三步,如果上述的请求不能成功,就要执行下边的代码了,
下边的代码,用一句话介绍:请求失败后,将当前线程链入队尾并挂起,之后等待被唤醒。在你看下边的代码的时候心里默记着这句话。
3.3.2、AbstractQueuedSynchronizer:addWaiter(Node mode)
/**
* 将Node节点加入等待队列
* 1)快速入队,入队成功的话,返回node
* 2)入队失败的话,使用正常入队
* 注意:快速入队与正常入队相比,可以发现,正常入队仅仅比快速入队多而一个判断队列是否为空且为空之后的过程
* @return 返回当前要插入的这个节点,注意不是前一个节点
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//创建节点
/*
* 快速入队
*/
Node pred = tail;//将尾节点赋给pred
if (pred != null) {//尾节点不为空
node.prev = pred;//将尾节点作为创造出来的节点的前一个节点,即将node链接到为节点后
/**
* 基于CAS将node设置为尾节点,如果设置失败,说明在当前线程获取尾节点到现在这段过程中已经有其他线程将尾节点给替换过了
* 注意:假设有链表node1-->node2-->pred(当然是双链表,这里画成双链表才合适),
* 通过CAS将pred替换成了node节点,即当下的链表为node1-->node2-->node,
* 然后根据上边的"node.prev = pred"与下边的"pred.next = node"将pred插入到双链表中去,组成最终的链表如下:
* node1-->node2-->pred-->node
* 这样的话,实际上我们发现没有指定node2.next=pred与pred.prev=node2,这是为什么呢?
* 因为在之前这两句就早就执行好了,即node2.next和pred.prev这连个属性之前就设置好了
*/
if (compareAndSetTail(pred, node)) {
pred.next = node;//将node放在尾节点上
return node;
}
}
enq(node);//正常入队
return node;
}
AbstractQueuedSynchronizer:enq(final Node node)
/**
* 正常入队
* @param node
* @return 之前的尾节点
*/
private Node enq(final Node node) {
for (;;) {//无限循环,一定要阻塞到入队成功为止
Node t = tail;//获取尾节点
if (t == null) { //如果尾节点为null,说明当前等待队列为空
/*Node h = new Node(); // Dummy header
h.next = node;
node.prev = h;
if (compareAndSetHead(h)) {//根据代码实际上是:compareAndSetHead(null,h)
tail = node;
return h;
}*/
/*
* 注意:上边注释掉的这一段代码是jdk1.6.45中的,在后来的版本中,这一段改成了如下这段
* 基于CAS将新节点(一个dummy节点)设置到头上head去,如果发现内存中的当前值不是null,则说明,在这个过程中,已经有其他线程设置过了。
* 当成功的将这个dummy节点设置到head节点上去时,我们又将这个head节点设置给了tail节点,即head与tail都是当前这个dummy节点,
* 之后有新节点入队的话,就插入到该dummy之后
*/
if (compareAndSetHead(new Node()))
tail = head;
} else {//这一块儿的逻辑与快速入队完全相同
node.prev = t;
if (compareAndSetTail(t, node)) {//尝试将node节点设为尾节点
t.next = node;//将node节点设为尾节点
return t;
}
}
}
}
注意:这里就是一个完整的入队方法,具体逻辑看注释和ReentrantLock:lock()的注释部分的相关部分。
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。