5. AQS(AbstractQueuedSynchronizer)抽象的队列式的同步器
5.1 AbstractQueuedSynchronizer里面的设计模式--模板模式
模板模式:父类定义好了算法的框架,第一步做什么第二步做什么,同时把某些步骤的实现延迟到子类去实现。
5.1.1 模板方法,jdk已经实现好的方法,子类直接使用即可
独占式获取
- acquire(int arg)
- acquireInterruptibly(int arg)
- tryAcquireNanos(int arg, long nanosTimeout)
共享式获取
- acquireShared(int arg)
- acquireSharedInterruptibly(int arg)
- tryAcquireSharedNanos(int arg, long nanosTimeout)
独占式释放
- release
共享式释放
- releaseShared(int arg)
5.1.2 子类需要实现的方法
- tryAcquire(int arg) 独占式获取
- tryRelease(int arg) 独占式释放
- tryAcquireShared(int arg) 共享式获取
- tryReleaseShared(int arg) 共享式释放
- isHeldExclusively() 这个同步器是否处于同步模式
5.2 Node
5.2.1 waitStatus的值
- CANCELLED = 1
表示如果线程中断或者等待超时,会被移出同步队列。 - SIGNAL -1
表示后续的节点处于等待状态,当前节点通知后续节点运行。 - CONDITION -2
表示节点处于condition等待队列里面 - PROPAGATE -3
共享模式,表示状态要往后面的节点传播
5.2.2 设置和获取state的值
- getState()
- setState()
- compareAndSetState() 远志操作设置
5.3 Condition
略
5.4 ReentrantLock为例子分析AQS
5.4.1 acquire(int) 独占模式的顶层获取资源的顶层方法
//1。lock方法,先通过原子操作设置state的值为1。
final void lock() {
if (compareAndSetState(0, 1))
//1.1如果设置成功,当前线程设置为占有锁的线程。
setExclusiveOwnerThread(Thread.currentThread());
else
//1.2否则调用acquire获取
acquire(1);
}
//2.acquire(1) 获取资源,即使等待,直到获取成功为止。
public final void acquire(int arg) {
//如果tryAcquire返回true直接短路
//否则在addWaiter把当前线程打包成一个Node节点放到同步队列队尾
//通过
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//2.1 tryAcquire(arg)
//子类需要自己实现tryAcquire逻辑。主要通过(getState/setState/compareAndSetState),是否可重入,是否阻塞
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//2.2 addWaiter(Node.EXCLUSIVE)
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;
//先通过原子操作compareAndSetTail,尝试把当前node加到同步队列的尾部。
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//上一步失败则通过enq入队。
enq(node);
return node;
}
//2.2。1 enq方法
private Node enq(final Node node) {
//使用自旋的方式
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 队列为空,创建一个空的标志结点作为head结点,并将tail也指向它。
if (compareAndSetHead(new Node()))
tail = head;
} else {
//队列不为空。则把当前node加入队尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
nq(node)
//2.3 acquireQueued(Node, int) 线程已经加入队尾,处于等待状态。直到其他线程释放线程唤醒自己。自己获取资源后,再处理自己的逻辑。
final boolean acquireQueued(final Node node, int arg) {
//是否获得资源,true表示未获得
boolean failed = true;
try {
//是否被中断过
boolean interrupted = false;
for (;;) {//自旋的方式
//获取前驱节点
final Node p = node.predecessor();
//如果前驱节点是head节点,那么当前节点是第二个节点。
//并且尝试获取资源成功,则设置当前节点为head
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果获取失败。将前驱节点的waitStatus改为Node.SIGNAL,就是告诉前驱节点,如果你释放了资源通知我一下。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//2.3.1 shouldParkAfterFailedAcquire(Node,Node) 获取资源失败后,判断前驱节点的waitStatus的值,1)如果前驱节点是取消状态舍弃;2)如果是正常状态设置前驱节点的状态是Node.SIGNAL并且返回true;3)如果前驱节点状态时Node.SIGNAL,直接返回true。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果waitStatus = Node.SIGNAL,返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//如果waitStatus = Node.CANCELLED,则舍弃这个前驱节点。继续判断下一个前驱节点的waitStatus
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,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//2.3.2 parkAndCheckInterrupt() 如果当前节点的前驱节点是Node.SIGNAL,则把当前线程挂起,处于等待状态。
private final boolean parkAndCheckInterrupt() {
//park的县城会处于waiting状态Thread。State。WAITING,通过unpark方法或者interrupt()方法唤醒
LockSupport.park(this);
return Thread.interrupted();
}
5.4.2 release(int) 独占式模式下释放资源
//1.独占模式下释放资源 一般都回成功,返回true
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;
}
//2. tryRelease(int) 子类自己实现的方法,用于释放资源。
//一般会释放成功。因为只有获取了资源的线程才会释放资源,直接减去相应量的资源。不用考虑线程安全问题。不需要原则操作。
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//3. unparkSuccessor(Node) 唤醒下一个节点
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.
*/
int ws = node.waitStatus;
if (ws < 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 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);
}
5.4.3 acquireShared(int) 共享模式下获取资源
//tryAcquireShared方法尝试获取资源,成功则直接返回。
//aqs里面已经定义好,获取失败小于0。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//2.doAcquireShared 如果调用tryAcquireShared获取失败,这里则调用doAcquireShared把节点放入同步队列尾部,并且通过park()让其休息,直到有线程唤醒它。
privateprivate void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
} void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 3。 setHeadAndPropagate(node, r); 将head指向自己,如果还有剩余量,则继续花唤醒下一个邻居线程。
5.4.4 releaseShared() 共享模式下释放资源
//1.releaseShared 先调用tryReleaseShared方法尝试释放
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//2。唤醒后继节点
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);//唤醒后继
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)// head发生变化
break;
}
}
独占模式下的tryRelease()在完全释放掉资源(state=0)后,才会返回true去唤醒其他线程,这主要是基于独占下可重入的考量;而共享模式下的releaseShared()则没有这种要求,共享模式实质就是控制一定量的线程并发执行,那么拥有资源的线程在释放掉部分资源时就可以唤醒后继等待结点。例如,资源总量是13,A(5)和B(7)分别获取到资源并发运行,C(4)来时只剩1个资源就需要等待。A在运行过程中释放掉2个资源量,然后tryReleaseShared(2)返回true唤醒C,C一看只有3个仍不够继续等待;随后B又释放2个,tryReleaseShared(2)返回true唤醒C,C一看有5个够自己用了,然后C就可以跟A和B一起运行。而ReentrantReadWriteLock读锁的tryReleaseShared()只有在完全释放掉资源(state=0)才返回true,所以自定义同步器可以根据需要决定tryReleaseShared()的返回值。
5. AQS(AbstractQueuedSynchronizer)抽象的队列式的同步器的更多相关文章
- java1.8 AQS AbstractQueuedSynchronizer学习
AQS concurrent并发包中非常重要的顶层锁类,往往用的比较多的是ReentrantLock,然而ReentrantLock的实现依赖AbstractQueuedSynchronizer在到上 ...
- 高并发第十一弹:J.U.C -AQS(AbstractQueuedSynchronizer) 组件:Lock,ReentrantLock,ReentrantReadWriteLock,StampedLock
既然说到J.U.C 的AQS(AbstractQueuedSynchronizer) 不说 Lock 是不可能的.不过实话来说,一般 JKD8 以后我一般都不用Lock了.毕竟sychronize ...
- AQS(一) 对CLH队列的增强
基本概念 AQS(AbstractQueuedSynchronizer),顾名思义,是一个抽象的队列同步器. 它的队列是先进先出(FIFO)的等待队列 基于这个队列,AQS提供了一个实现阻塞锁的机制 ...
- AQS(AbstractQueuedSynchronizer)介绍-01
1.概述 AQS( AbstractQueuedSynchronizer ) 是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很容易并且高效地构造出来.如: ReentrantLock 和 ...
- 从ReentrantLock看AQS (AbstractQueuedSynchronizer) 运行流程
从ReentrantLock看AQS (AbstractQueuedSynchronizer) 运行流程 概述 本文将以ReentrantLock为例来讲解AbstractQueuedSynchron ...
- 【读书笔记】iOS网络-同步请求,队列式异步请求,异步请求的区别
一,同步请求的最佳实践. 1,只在后台过程中使用同步请求,除非确定访问的是本地文件资源,否则请不要在主线程上使用. 2,只有在知道返回的数据不会超出应用的内存时才使用同步请求.记住,整个响应体都会位于 ...
- 【读书笔记】iOS-网络-同步请求,队列式异步请求,异步请求的区别
一,同步请求的最佳实践. 1,只在后台过程中使用同步请求,除非确定访问的是本地文件资源,否则请不要在主线程上使用. 2,只有在知道返回的数据不会超出应用的内存时才使用同步请求.记住,整个响应体都会位于 ...
- 并发之AQS原理(二) CLH队列与Node解析
并发之AQS原理(二) CLH队列与Node解析 1.CLH队列与Node节点 就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队. 一条队列是队列中每一个人 ...
- AQS(抽象队列同步器)
AQS(全称为AbstractQueuedSynchronizer),即抽象队列同步器,它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列. state的访问方 ...
随机推荐
- ASP.NET框架获取数据字典数据做成树的格式
private List<TreeEntity> treeList = new List<TreeEntity>();//创建一个树的List集合 public ActionR ...
- (转)什么?你还不会写JQuery 插件
原文地址:http://www.cnblogs.com/joey0210/p/3408349.html 前言 如今做web开发,jquery 几乎是必不可少的,就连vs神器在2010版本开始将Jque ...
- 20169219 实验四Android程序设计
一.实现Linux下dc的功能,计算后缀表达式的值 public int evaluate(String expr) { int op1, op2, result = 0; String token; ...
- 快速入手Web幻灯片制作
在线幻灯片 使用markdown可以快速的写出优美的文档,接下来我介绍一些简单的语法,快速的用浏览器制作幻灯片. 最基本使用格式 <!DOCTYPE html> <html> ...
- element时间选择器插件转化为YYYY-MM-DD的形式
let datete = new Date(this.form.value0);this.form.value0 =datete.getFullYear() +"-" +(date ...
- Log--事务日志
由于日志是顺序写入,而修改数据分散在数据库各个页面,属于随机写入,而磁盘顺序写入速度远高于随机写入,因此主流数据库都采用预写日志的方式来确保数据完整性 1.日志记录的是数据的变化而不是引发数据的操作2 ...
- 团队内的沟通方式:网络 OR 当面
4月1日,我和老王稍微聊了聊关于互联网的工作方式,团队的成员不在一个地方,通过远程互联网的方式进行沟通是不是OK.现在确实有很多公司是有多地的分公司,子公司,不同的团队在不同的办公室中,甚至一个大团队 ...
- day01.2-python基础
一. python基本数据类型及操作 1. 数字.在python中,数字的初始化方式为直接赋值.如:a = 11 a). 加法运算 b ...
- ajax-2
serialize()输出序列化表单值的结果: 如: <html> <head> <script type="text/javascript" src ...
- 关于jboss的线程问题+java.lang.outofmemoryError
近日来,用Jmeter做压力测试.发现,每台客户机使用800个线程组压力倍增.昨天的测试,到了今天下午都没有跑完. 仔细观察了下Jboss的错误日志,发现,jboss已经宕机了. 本身后台的环境是使用 ...