底层数据结构
在【Java并发编程实战】—–“J.U.C”:CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形。其主要从两方面进行了改造:节点的结构与节点等待机制。在结构上引入了头结点和尾节点,他们分别指向队列的头和尾,尝试获取锁、入队列、释放锁等实现都与头尾节点相关,并且每个节点都引入前驱节点和后后续节点的引用;在等待机制上由原来的自旋改成阻塞唤醒
1、volatile int state ,volatile保证state线程间可见和禁止指令重排序,关于volatile用法,后续在其他文章中补充
2、等待队列,CAS操作(compareAndSet或者叫compareAndSwap,不同场景不同叫法)
掌握这个底层原理,JUC并发包下几乎所有的类,比如常用的ReentrantLock、CountDownLatch、CyclicBarrier、Phaser、Semaphore、LockSupport等。
后续计划:
- java并发编程JUC并发包之ReentrantLock
- java并发编程JUC并发包之CountDownLatch
- java并发编程JUC并发包之CyclicBarrier
- java并发编程JUC并发包之Phaser
- java并发编程JUC并发包之Semaphore
- java并发编程JUC并发包之LockSupport
源码分析
AbstractQueuedSynchronizer类底层的数据结构是使用双向链表,是队列的一种实现,故也可看成是队列,其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。
继承关系
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414691L;、
...
//等待队列头指针
private transient volatile Node head;
//等待队列尾指针
private transient volatile Node tail;
//同步状态,可用于计数,如ReentrantLock的可重入特性、CountDownLatch的计数等
private volatile int state;
//自旋时间,单位纳秒
static final long spinForTimeoutThreshold = 1000L;
...
}
父类AbstractOwnableSynchronizer:
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
// 版本序列号
private static final long serialVersionUID = 3737899427754241961L;
// 构造函数
protected AbstractOwnableSynchronizer() { }
// 独占模式下的线程
private transient Thread exclusiveOwnerThread;
// 设置独占线程
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
// 获取独占线程
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
内部类
1、Node类
static final class Node {
// 模式,分为共享与独占
// 共享模式
static final Node SHARED = new Node();
// 独占模式
static final Node EXCLUSIVE = null;
// 结点状态
// CANCELLED,值为1,表示当前的线程被取消
// SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
// CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
// PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
// 值为0,表示当前节点在sync队列中,等待着获取锁
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// 结点状态
volatile int waitStatus;
// 前驱结点
volatile Node prev;
// 后继结点
volatile Node next;
// 结点所对应的线程
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;
}
}
2、ConditionObject类
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
//condition队列头节点
private transient Node firstWaiter;
//condition队列尾节点
private transient Node lastWaiter;
public ConditionObject() { }
//在尾节点后面添加封装了当前线程的Node
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();//unlink时会更新lastWaiter
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
...
}
常用方法
acquire方法
该函数以独占模式获取(资源),忽略中断,即线程在aquire过程中,中断此线程是无效的。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 首先调用tryAcquire函数,调用此方法的线程会试图在独占模式下获取对象状态。此方法应该查询是否允许它在独占模式下获取对象状态,如果允许,则获取它。在AbstractQueuedSynchronizer源码中默认会抛出一个异常,即需要子类去重写此函数完成自己的逻辑。
- 若tryAcquire失败,则调用addWaiter函数,addWaiter函数完成的功能是将调用此方法的线程封装成为一个结点并放入Sync queue。
- 调用acquireQueued函数,此函数完成的功能是Sync queue中的结点不断尝试获取资源,若成功,则返回true,否则,返回false。
下面看addWaiter方法:
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;
//使用快速加入到尾节点的方法,比较pred是否为尾结点,是则将尾结点设置为node
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果快速加入失败,调用enq()方法,内部是无限循环+cas
enq(node);
return node;
}
acquireQueue方法:
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);
}
}
首先获取当前节点的前驱节点,如果前驱节点是头结点并且能够获取(资源),代表该当前节点能够占有锁,设置头结点为当前节点,返回。否则,调用shouldParkAfterFailedAcquire和parkAndCheckInterrupt函数
release方法
//以独占模式释放对象
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
// 头结点不为空并且头结点状态不为0
if (h != null && h.waitStatus != 0)
//释放头结点的后继结点
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease的默认实现是抛出异常,需要具体的子类实现,如果tryRelease成功,那么如果头结点不为空并且头结点的状态不为0,则释放头结点的后继结点
unparkSuccessor方法:
// 释放后继结点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
// 获取node结点的等待状态
int ws = node.waitStatus;
if (ws < 0) // 状态值小于0,为SIGNAL -1 或 CONDITION -2 或 PROPAGATE -3
// 比较并且设置结点等待状态,设置为0
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 获取node节点的下一个结点
Node s = node.next;
if (s == null || s.waitStatus > 0) { // 下一个结点为空或者下一个节点的等待状态大于0,即为CANCELLED
// s赋值为空
s = null;
// 从尾结点开始从后往前开始遍历
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) // 找到等待状态小于等于0的结点,找到最前的状态小于等于0的结点
// 保存结点
s = t;
}
if (s != null) // 该结点不为为空,释放许可
LockSupport.unpark(s.thread);
}
总结
对于AbstractQueuedSynchronizer的分析,最核心的就是sync queue的分析。
- 每一个结点都是由前一个结点唤醒
- 当结点发现前驱结点是head并且尝试获取成功,则会轮到该线程运行。
- condition queue中的结点向sync queue中转移是通过signal操作完成的。
- 当结点的状态为SIGNAL时,表示后面的结点需要运行。
参考文档:https://www.cnblogs.com/leesf456/p/5350186.html