Java多线程(五)AQS-抽象队列同步器解析
2019-10-13 本文已影响0人
GIT提交不上
一、同步器设计
AQS,全称为AbstractQueuedSynchronizer(抽象队列同步器),是Java多线程显式锁(Lock)的底层实现。同步器的设计属于独占模式:资源是独占的,一次只能一个线程获取。同步器的具体设计方案如下:
- 定义一个变量int state=0,变量表示为被获取的资源数量。
- 线程在获取资源前先检查state的状态,如果为0,则修改为1,表示获取资源成功,否则表示资源已经被其他线程占用,此时线程要堵塞以等待其他线程释放资源。
- 为了能使得资源释放后找到那些为了等待资源而堵塞的线程,我们把这些线程保存在FIFO队列中。
- 当占有资源的线程释放掉资源后,可以从队列中唤醒一个堵塞的线程,由于此时资源已经释放,因此这个被唤醒的线程可以获取资源并且执行。
参考链接:JUC解析-AQS(1)
AQS模型如图1-1所示:
图1-1 AQS模型.png二、AQS成员变量
AQS底层实现是双向链表的数据结构,通过查看源码,其主要包含的成员变量包括:
//状态变量state
private volatile int state;
//双向链表表头
private transient volatile Node head;
//双向链表表尾
private transient volatile Node tail;
Node类源码如下所示:
static final class Node {
//标记一个结点(对应的线程)在共享模式下等待
static final Node SHARED = new Node();
// 标记一个结点(对应的线程)在独占模式下等待
static final Node EXCLUSIVE = null;
//waitStatus的值,表示该结点(对应的线程)已被取消
static final int CANCELLED = 1;
//waitStatus的值,表示后继结点(对应的线程)需要被唤醒
static final int SIGNAL = -1;
//waitStatus的值,表示该结点(对应的线程)在等待某一条件
static final int CONDITION = -2;
//waitStatus的值,表示有资源可用,新head结点需要继续唤醒后继结点
static final int PROPAGATE = -3;
volatile int waitStatus; //等待状态
volatile Node prev; //前驱节点
volatile Node next; //后继节点
volatile Thread thread; //节点对应的线程
Node nextWaiter; //等待队列下一个等待的节点
//其余省略
... ... ... ...
}
三、acquire & release资源获取模式
acquire是一种以独占方式获取资源,如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。acquire方法如下所示:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1.tryAcquire()尝试直接去获取资源,如果成功则直接返回;
2.addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
3.acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
4.如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
release方法会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。release方法源码如下所示:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
如果资源释放成功,调用unparkSuccessor(Node)方法唤醒等待队列中下一个线程。这里要注意的是,下一个线程并不一定是当前节点的next节点,而是下一个可以用来唤醒的线程,如果这个节点存在,调用unpark()方法唤醒。unparkSuccessor方法源码如下所示:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
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);
}
参考链接:Java技术之AQS详解
未完待续