AQS原理及应用
To use this class as the basis of a synchronizer, redefine the
* following methods, as applicable, by inspecting and/or modifying
* the synchronization state using {@link #getState}, {@link
* #setState} and/or {@link #compareAndSetState}:
*
* <ul>
* <li> {@link #tryAcquire}
* <li> {@link #tryRelease}
* <li> {@link #tryAcquireShared}
* <li> {@link #tryReleaseShared}
* <li> {@link #isHeldExclusively}
* </ul>
上面这段话是AQS源码的一段注解,意思是使用AQS实现一个同步器的话需要覆盖实现上面li标签中的这些方法,并且使用getState、setState、compareAndSetState这几个方法来对状态进行操作。
如果你对JDK源码较为熟悉的话,你会发现AQS(AbstractQueuedSynchronizer)是并发过程中很常见的一个抽象类,我们常用的CountDownLatch、ReentrantLock、FutureTask(1.8不再使用AQS)、Semaphore等类都是在内部定义了一个叫做Sync的内部类,而Sync类继承了AQS抽象类并重写了一些必要的方法。可重入锁ReentrantLock中,state可以用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来说,state的高16位表示获取到的读锁的线程的可重入次数,低16位是写锁;对于Semaphore来说,state用来表示当前可用信号的个数;对于CountDownlatch来说,state用来表示计数器当前的值
abstract static class Sync extends AbstractQueuedSynchronizer {
...
...
...
}
下面说说AQS到底是用来做什么的:
AQS抽象类的内部是一个final的Node类,类中定义了一个volatile的整形状态量state,通过这个Node类来建立一个FIFO的线程等待队列(多线程抢占资源失败被阻塞时会进入此队列)。
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
}
而这个队列中的每个结点对应一个线程,结点内封装了这个线程的基本信息,状态和等待的资源类型等。
* <p>To enqueue into a CLH lock, you atomically splice it in as new
* tail. To dequeue, you just set the head field.
* <pre>
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
* </pre>
*
* <p>Insertion into a CLH queue requires only a single atomic
* operation on "tail", so there is a simple atomic point of
* demarcation from unqueued to queued. Similarly, dequeuing
* involves only updating the "head". However, it takes a bit
* more work for nodes to determine who their successors are,
* in part to deal with possible cancellation due to timeouts
* and interrupts.
这里借用网上的一张图来说明这个队列的框架。
AQS定义了两种资源共享的方式,独占和共享,Exclusive和Share,前者常用的是ReentrantLock,后者常用的是前面提到的CountDownLatch、Semaphore等。
继承AQS,实现state标志位的获取、释放方式,即可实现自定义同步器,至于具体的等待队列的维护、阻塞入队、唤醒出队等在AQS中已经实现好了。标志位涉及到的实现函数如下:
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
自定义同步器
独占锁以ReentrantLock为例,state初始值为0,表示未锁状态,当一个线程执行了lock()方法之后,会调用tryAcquire方法独占该锁并将state+1,此后其他线程尝试获取锁时便会失败,进入队列,知道刚才的线程释放锁将state值更改为0时,其他线程才有机会获取这个独占锁。当然,这个锁时可重入的,获得锁的线程可以继续state+1,不过只有state为0的时候其他线程才会唤醒。
共享锁以CountDownLatch为例,初始化线程数为n,及将state初始值设置为n,表示可以有n个线程同时获取这个共享锁,当有线程执行一countDown(),state就会CAS的方式减少1,知道所有线程执行完,state值为0的时候,会unpark主线程,然后主线程会从await状态被唤醒返回,继续执行其他指令。
一般情况自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。需要说明的是AQS也支持同时实现独占同步和共享同步,比如ReentrantReadWriteLock。
通常,如果一个线程没有拿到锁,便会被封装成结点加入队尾,检查状态之后会调用park方法进入waiting状态,等待unpark方法或interrupt方法唤醒自己,当它被唤醒之后会检查自己是否有资格拿到锁,如果成功,head会指向当前节点,并检查是否被中断过;否则会继续等待。
AQS是支持中断的,比如acquireInterruptibly方法和acquireSharedInterruptibly方法。下面将书上看到的互斥锁Mutex源码贴在下面供大家研究,它不支持重入,只有0(未锁定)和1(锁定)两种状态:
class Mutex implements Lock, java.io.Serializable {
// 自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 判断是否锁定状态
protected boolean isHeldExclusively() {
return getState() == 1;
} // 尝试获取资源,立即返回。成功则返回true,否则false。
public boolean tryAcquire(int acquires) {
assert acquires == 1; // 这里限定只能为1个量
if (compareAndSetState(0, 1)) {//state为0才设置为1,不可重入!
setExclusiveOwnerThread(Thread.currentThread());//设置为当前线程独占资源
return true;
}
return false;
} // 尝试释放资源,立即返回。成功则为true,否则false。
protected boolean tryRelease(int releases) {
assert releases == 1; // 限定为1个量
if (getState() == 0)//既然来释放,那肯定就是已占有状态了。只是为了保险,多层判断!
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);//释放资源,放弃占有状态
return true;
}
} // 真正同步类的实现都依赖继承于AQS的自定义同步器!
private final Sync sync = new Sync(); //lock<-->acquire。两者语义一样:获取资源,即便等待,直到成功才返回。
public void lock() {
sync.acquire(1);
} //tryLock<-->tryAcquire。两者语义一样:尝试获取资源,要求立即返回。成功则为true,失败则为false。
public boolean tryLock() {
return sync.tryAcquire(1);
} //unlock<-->release。两者语文一样:释放资源。
public void unlock() {
sync.release(1);
} //锁是否占有状态
public boolean isLocked() {
return sync.isHeldExclusively();
}
}
AQS原理及应用的更多相关文章
- AQS原理以及AQS同步组件总结
1 AQS简单介绍 2 AQS原理
- 并发之AQS原理(三) 如何保证并发
并发之AQS原理(三) 如何保证并发 1. 如何保证并发 AbstractQueuedSynchronizer 维护了一个state(代表了共享资源)和一个FIFO线程等待队列(多线程竞争资源被阻塞时 ...
- 并发之AQS原理(一) 原理介绍简单使用
并发之AQS原理(一) 如果说每一个同步的工具各有各的强大,那么这个强大背后是一个相同的动力,它就是AQS. AQS是什么 AQS是指java.util.concurrent.locks包里的Abst ...
- 简述AQS原理
这是一道面试题:简述AQS原理 AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态.如果被请求的共享资源被占用,那么就需要一套线程阻塞 ...
- AQS原理
1. AQS原理 1.1. 是什么 AQS全程AbstractQueuedSynchronizer抽象队列同步器,它是并发包中的基础类 ReetrantLock,ReentrantReadWriteL ...
- 并发之AQS原理(二) CLH队列与Node解析
并发之AQS原理(二) CLH队列与Node解析 1.CLH队列与Node节点 就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队. 一条队列是队列中每一个人 ...
- AQS 原理以及 AQS 同步组件总结
1 AQS 简单介绍 AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面. AQS 是一个用来构建锁和同步 ...
- 浅谈AQS原理
一.AQS介绍 AQS,即AbstractQueuedSynchronizer, 抽象队列同步器,它是Java多线程模块用来构建锁和其他同步组件的基础框架.来看下同步组件对AQS的使用: AQS是一个 ...
- Java并发之AQS原理解读(三)
上一篇:Java并发之AQS原理解读(二) 前言 本文从源码角度分析AQS共享锁工作原理,并介绍下使用共享锁的子类如何工作的. 共享锁工作原理 共享锁与独占锁的不同之处在于,获取锁和释放锁成功后,都会 ...
- Java并发之AQS原理解读(二)
上一篇: Java并发之AQS原理解读(一) 前言 本文从源码角度分析AQS独占锁工作原理,并介绍ReentranLock如何应用. 独占锁工作原理 独占锁即每次只有一个线程可以获得同一个锁资源. 获 ...
随机推荐
- windows/Linux/Mac下安装maven,maven作用
Linux下安装maven 1.首先到Maven官网下载安装文件,目前最新版本为3.0.3,下载文件为apache-maven-3.3.9-bin.tar.gz,下载可以使用wget命令: 2.进入下 ...
- poj 3572 Hanoi Tower
Hanoi Towers Time Limit : 10000/5000ms (Java/Other) Memory Limit : 131072/65536K (Java/Other) Total ...
- Codeforces Round #172 (Div. 1 + Div. 2)
A. Word Capitalization 模拟. B. Nearest Fraction 枚举. C. Rectangle Puzzle 求出两个矩形的点,套简单多边形的面积交板子. D. Max ...
- 【机器学习】【条件随机场CRF-2】CRF的预测算法之维特比算法(viterbi alg) 详解 + 示例讲解 + Python实现
1.CRF的预测算法条件随机场的预测算法是给定条件随机场P(Y|X)和输入序列(观测序列)x,求条件概率最大的输出序列(标记序列)y*,即对观测序列进行标注.条件随机场的预测算法是著名的维特比算法(V ...
- 如何用简单易懂的例子解释条件随机场(CRF)模型?它和HMM有什么区别?
https://www.zhihu.com/question/35866596/answer/418341940
- excel中如何筛选功能的使用
excel中如何筛选功能的使用 excel是一款数据处理工具,可以在众多的数据中找到想要的经过处理之后的数据,而最直接方便的功能就是筛选.请阅读下文,了解如何对数据进行筛选. 如下图所示的学生成绩中, ...
- Linux下的实用工具——计算器bc
Linux下的实用工具——计算器 1. bc指令算加法,如图: 4. bc指令算除法(进阶),如图示,10/3之所以为3,是因为我们没有指定小数点后取几位,默认取到整数部分:而10/100之所以为 ...
- C# 序列类为 xml 可以使用的特性大全
本文告诉大家如何使用序列类,以及序列时可以用到的特性,特性的作用和一些容易被问的问题 最近我在把项目文件修改为 VisualStudio 2017 的格式,请看从以前的项目格式迁移到 VS2017 新 ...
- 【u212】&&【t036】最大和
Time Limit: 1 second Memory Limit: 128 MB [问题描述] N个数围成一圈,要求从中选择若干个连续的数(注意每个数最多只能选一次)加起来,问能形成的最大的和. [ ...
- MySQL排序问题
直接上SQL语句: SELECT * FROM user_test ORDER BY user_name ,user_money DESC; #user_name中如果是数字会先排在前面,然后在按字母 ...