AQS详解(AbstractQueuedSynchronizer)
Intrinsic VS explicity
1. 不一定保证公平 1. 提供公平和非公平的选择
2. 无 2. 提供超时的功能
3. 无 3. 提供对中断的响应
4. 无 4. 提供try... 的方法
5. 保证获取锁和释放锁的一致性 5. 需要自己保证
6. 只有一个conditionQueue 6. 可以有多个conditonQueue
7. 使用灵活
锁: 一个带有内部状态的类
自定义同步协议需要注意的点:
- 获取锁的时候, 状态如何变化
- 释放锁的时候, 状态如何变化, 已经状态变化后是都对别的等待方有影响
AQS的作用
- 提供对获取锁成功或失败后, 线程的管理, 比如是否进行阻塞
- 当释放锁后, 提供对等待的线程的通知功能
- 提供给用户对状态进行自定义处理的接口
AQS的使用
- AQS依赖于一个FIFO队列和一个int表示的状态. 具体的实现类可以自己去保存别的状态, 但是只有这个int表示的状态是require和release的, 子类自己的状态是做辅助作用的.
2. 想要使用AQS来作为一个同步器的基类,需要实现下面这几个方法.
tryAcquire
tryRelease
tryAcquireShared
tryReleaseShared
isHeldExclusively
(注意, 一定要保证线程安全)
3. 可以通过这些方法来对状态进行监控和修改
getState
setState
compareAndSetState
源码分析
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
/**addWaiter()将新node加到队列的末尾, 如果队列没有初始化的话,进行初始化操作(如果head节点是null的话,创建一个新的node当head节点)
acquireQueued() 如果当前节点是head的下一个节点,进行获取state操作,成功后不进行阻塞. 否则,在链表中向前寻找,直到找到一个没有被取消的节点,把当前节点作为找到的节点的下一个节点,然后把当前节点阻塞.
cancelAcquire() 把当前节点的状态改为取消状态,然后向前找,直到找到一个没有被取消的节点,如果这个节点不是头节点,而且没有被取消,则把当前节点的下一个节点作为找到的这个节点的子节点. 否则,找到当前节点的下一个没有被取消的节点,然后把它唤醒.
**/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 找到下一个没有被取消的节点,然后把他唤醒.
unparkSuccessor(h);
return true;
}
return false;
}
Release操作不会去操作队列,只会去唤醒后边的没有取消的节点.而且release后会把当前节点的状态改为0.
Head节点可能是初始化的那个new node()节点, 也有可能是获取状态成功的那个节点.
所以,即使头节点的状态是0, 后续来的节点也会把头节点的状态改为Signal, 除非后面已经没有等待的节点了.
就算中间的节点可能因为某种因素导致链条没有接上,也会从tail开始搜索, 来保证如果有是一定可以找到的. 就算因为竞争的问题, 导致新加入的节点没有接收到前面节点的通知,但是,这种情况下, 前面的节点,除了头节点,都已经被取消了, 所以在enq()操作的时候, 会直接去tryAcquire,也不会导致整个操作有问题. 还有一个保障措施是只有当前面的节点是signal的时候, 才会安心的去block,而在reslease这边,会把这个状态改成0,然后再去唤醒后边的几点.
需要注意的是, 如果我们的release操作和acquire操作中,对state的状态改动不一致,会导致死锁.
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
// 和非中断的操作的区别是, 当当前节点被唤醒的时候, 如果被中断过,会抛出中断异常
doAcquireInterruptibly(arg);
}
/**
* Attempts to acquire in exclusive mode, aborting if interrupted,
* and failing if the given timeout elapses. Implemented by first
* checking interrupt status, then invoking at least once {@link
* #tryAcquire}, returning on success. Otherwise, the thread is
* queued, possibly repeatedly blocking and unblocking, invoking
* {@link #tryAcquire} until success or the thread is interrupted
* or the timeout elapses. This method can be used to implement
* method {@link Lock#tryLock(long, TimeUnit)}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
* @param nanosTimeout the maximum number of nanoseconds to wait
* @return {@code true} if acquired; {@code false} if timed out
* @throws InterruptedException if the current thread is interrupted
*/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
// 和正常的区别是, 在shouldpark后, 会判断时间的长短,短的话, 会进行自旋. 长的话, 会调用parkNanos()进行阻塞
doAcquireNanos(arg, nanosTimeout);
}
/**
* Acquires in shared mode, ignoring interrupts. Implemented by
* first invoking at least once {@link #tryAcquireShared},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquireShared} until success.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquireShared} but is otherwise uninterpreted
* and can represent anything you like.
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
// 和正常的区别是, 在成功获取状态后,在setHead后,如果tryAcquire返回的值大于0,而且下一个节点也是shared的节点,且当前节点的状态是signal. 则唤醒下一个节点. 如果当前节点的状态是0,则改为PROPAGATE
doAcquireShared(arg);
}
/**
* Release action for shared mode -- signal successor and ensure
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
和release的区别是, 如果head的状态是0 , 会改为PROPAGATE
private void doReleaseShared() {
/*
Condition
/**
* Implements interruptible condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with
* saved state as argument, throwing
* IllegalMonitorStateException if it fails.
* <li> Block until signalled or interrupted.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 1如果最后一个节点被取消了, 就把整个链表中取消的节点都删掉
// 2 把这个节点加到链表的最后, 初始状态是condition
Node node = addConditionWaiter();
// 调用release操作, 把当前获取锁的node release掉(注意, 并没有保证conditionNode 和 当前持有锁的线程的node的对应)
int savedState = fullyRelease(node);
int interruptMode = 0;
// 判断这个node是否在SyncQueue 中
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
// 被唤醒或被中断
if ((interruptMode =
// 如果当前线程没有被中断过,break, 但是中断后可能会很诡异
否则判断是否在signal之前取消了,如果是, 把这个node转到SyncQueue中, 状态从condition转到0,
如果状态不是condition, 一直等到这个节点被放到syncQueue中,然后返回false
checkInterruptWhileWaiting(node)) != 0)
break;
}
// 获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
// 看下是否被中断过, 然后对中断做响应.
reportInterruptAfterWait(interruptMode);
}
/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
// 所以使用condition必须实现isHeldExclusively 这个方法
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
/**
* Removes and transfers nodes until hit non-cancelled one or
* null. Split out from signal in part to encourage compilers
* to inline the case of no waiters.
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// 1. 把节点的状态从condition转到0, 如果失败, 返回false
2. 把这个节点插入到syncQueue中, 把这个节点的上一个节点置为signal. 如果上个节点是取消状态或者置为signal的操作失败,把这个节点的线程唤醒. 返回true. 这时这个节点已经在syncqueue中了, 整个流程可以正常跑通
// 结论: 将这个node的线程作为一个正常的获取锁的线程, 把他放到syncQueue中, 等待被唤醒, 如果没有头节点, 或错过了头节点的唤醒, 就直接唤醒,
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
AQS详解(AbstractQueuedSynchronizer)的更多相关文章
- (转)Java并发包基石-AQS详解
背景:之前在研究多线程的时候,模模糊糊知道AQS这个东西,但是对于其内部是如何实现,以及具体应用不是很理解,还自认为多线程已经学习的很到位了,贻笑大方. Java并发包基石-AQS详解Java并发包( ...
- AQS详解
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
- 【Java并发】详解 AbstractQueuedSynchronizer
前言 队列同步器 AbstractQueuedSynchronizer(以下简称 AQS),是用来构建锁或者其他同步组件的基础框架.它使用一个 int 成员变量来表示同步状态,通过 CAS 操作对同步 ...
- Java并发之AQS详解
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
- 【1】AQS详解
概述: 它内部实现主要是状态变量state和一个FIFO队列来完成,同步队列的头结点是当前获取到同步状态的结点,获取同步状态state失败的线程,会被构造成一个结点加入到同步队列尾部(采用自旋CAS来 ...
- Java并发之AQS详解(转)
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
- Java AQS详解(转)
原文地址 一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同 ...
- Java并发包基石-AQS详解
目录 1 基本实现原理 1.1 如何使用 1.2 设计思想 2 自定义同步器 2.1 同步器代码实现 2.2 同步器代码测试 3 源码分析 3.1 Node结点 3.2 独占式 3.3 共享式 4 总 ...
- Java中的锁原理、锁优化、CAS、AQS详解!
阅读本文大概需要 2.8 分钟. 来源:jianshu.com/p/e674ee68fd3f 一.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 二.锁实现的基本原理 2.1.v ...
随机推荐
- Haskell语言学习笔记(42)Bifunctor
Bifunctor class Bifunctor p where bimap :: (a -> b) -> (c -> d) -> p a c -> p b d bim ...
- Haskell语言学习笔记(35)Contravariant
contravariant 模块 contravariant 模块需要安装 $ cabal install contravariant contravariant-1.4 Prelude> :m ...
- time和datetime
一.time模块常用函数1. time()函数time()函数返回的是时间戳(timestamp).所谓时间戳指的是从1970年1月1日00:00:00开始按秒计算的偏移量.其他返回时间戳方式的函数还 ...
- 启动tomcat时,一直卡在Deploying web application directory
本来今天正常往服务器上扔一个tomcat 部署一个项目的, 最后再启动tomcat 的时候 发现项目一直都访问不了,看了一下日志: 1 2 3 4 5 6 7 [root@iz8vbdzx7y7owm ...
- JFinal架构简介
JFinal 是基于Java 语言的极速 web 开发框架,其核心设计目标是开发迅速.代码量少.学习简单.功能强大.轻量级.易扩展.Restful.在拥有Java语言所有优势的同时再拥有ruby.py ...
- [z]kafka相关资料
http://my.oschina.net/ielts0909/blog/93190 http://www.iteye.com/magazines/107 http://blog.csdn.net/h ...
- cvc-complex-type.2.4.a: Invalid content was found starting with element 'init-param'.
笔者最近学习一些spring mvc,在复制别人代码的时候报这个错.报错来源web.xml,原因是不符合xsd对xml的约束 源文件 <?xml version="1.0" ...
- 注册带有Portal功能的DYN365_ENTERPRISE_PLAN1地址
使用官方进入的注册页面注册后试用,发现没有Portal功能. https://trials.dynamics.com/Dynamics365/Signup 使用以下的地址注册可以产生Portal ht ...
- 使用PHP-GTK编写一个windows桌面应用程序
PHP-GTK的下载地址:http://gtk.php.net/download.php?language=en-US, 猿哥选择了最新版本(beta版),可能有人会问我们为啥不选最新的stable版 ...
- MySql中4种批量更新的方法
最近在完成MySql项目集成的情况下,需要增加批量更新的功能,根据网上的资料整理了一下,很好用,都测试过,可以直接使用. mysql 批量更新共有以下四种办法 1..replace into 批量更新 ...