Java多线程:AQS
在Java多线程:线程间通信之Lock中我们提到了ReentrantLock是API级别的实现,但是没有说明其具体实现原理。实际上,ReentrantLock的底层实现使用了AQS(AbstractQueueSynchronizer)。AQS本身仅仅是一个框架,定义了一套多线程访问共享资源的同步框架,可以实现ReentrantLock, Semaphore, CountDownLatch等多线程类。
AQS框架维护了一个资源state(volatile int)和一个同步队列。其中对state的访问包括三种方法:getState(), setState(), compareAndSetState()。其中,compareAndSetState()是原子操作,底层是CAS实现。
AQS框架包含两种可供选择的实现方式:独占(Exclusive)和共享(Share)。由于不同自定义同步器征用共享资源的方式不同,自定义同步器实现时只需实现共享资源state的获取与释放方式即可,而不需要考虑队列的维护。下面简述AQS框架中独占锁和共享锁的获取,释放流程。
独占锁流程
获取时首先调用acquire(acquires),之后进入tryAcquire(acquires)尝试获取锁,若成功则返回。若失败则将当前线程构造为Node节点,CAS插入到同步队列尾部,该线程自旋。自旋时判断其前驱节点是否为头节点,是否成功获取同步状态,二者皆成立则当前节点设置为头节点,否则挂起当前线程等待被前驱节点唤醒。
释放时首先调用release(acquires),之后进入tryRelease(acquires)释放同步状态,之后获取同步队列中当前节点的下一节点并唤醒。
共享锁流程
获取时首先调用acquireShared(acquires),之后进入tryAcquireShared(acquires)获取同步状态,返回值不小于0则说明同步状态有剩余,获取成功直接返回。若返回值小于0则说明获取同步状态失败,构造Node节点CAS插入同步队列尾部并自旋检查前驱节点是否为头节点且成功获取同步状态,若是则当前节点设为头节点,否则挂起等待被前驱节点唤醒。
释放时调用releaseShared(acquires)释放同步状态,之后遍历整个队列唤醒所有后继节点。
独占锁和共享锁实现区别
- 独占锁的state值为1,同一时刻只有一个线程成功获取同步状态。共享锁state>1,取值由自定义同步器决定。
- 独占锁队列头节点运行完毕释放锁后唤醒直接后继节点,共享锁唤醒所有后继节点。
- 共享锁会出现多个线程同时成功获取同步状态的情况。
重入锁的实现
Java中的ReentrantLock和synchronized都是可重入锁,synchronized由JVM实现,重入锁实现时最主要的逻辑是判断上次获取锁的线程是否为当前线程,ReentrantLock基于AQS实现,提供公平锁和非公平锁两种方式,非公平锁实现逻辑如下:
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//通过AQS获取同步状态
int c = getState();
//同步状态为0,说明临界区处于无锁状态,
if (c == 0) {
//修改同步状态,即加锁
if (compareAndSetState(0, acquires)) {
//将当前线程设置为锁的owner
setExclusiveOwnerThread(current);
return true;
}
}
//如果临界区处于锁定状态,且上次获取锁的线程为当前线程
else if (current == getExclusiveOwnerThread()) {
//则递增同步状态
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁的实现逻辑如下,与非公平锁的区别为判断当前节点是否存在前驱节点,只有等待前驱节点释放后才能获取锁。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//此处为公平锁的核心,即判断同步队列中当前节点是否有前驱节点
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
读写锁的实现
Java的ReentrantReadWriteLock是读写锁实现,其原理是将state变量的高16位和低16位拆分,高16位表示读锁,低16位表示写锁。其写锁tryAcquire(acquires)实现如下:
- 获取同步状态,分离出低16位的写锁状态。
- 同步状态不为0,则存在读锁或写锁。
- 若存在读锁,则不能获取写锁。
- 若当前线程不是上次获取写锁的线程,则不能获取写锁。
- 以上判断通过,对低16位(写锁同步状态)进行CAS修改。
- 当前线程设为写锁的获取线程。
其读锁的tryAcquire(acquires)实现如下:
- 获取当前同步状态,计算高16位为读锁状态+1后的值。
- 若大于能获取到的读锁的最大值,则抛出异常。
- 若存在写锁且当前线程不是写锁获取者,则获取读锁失败。
- 若上述判断都通过,则利用CAS重新设置读锁的同步状态。
写写锁释放与普通独占锁基本相同,在写锁释放中不断减少读锁的同步状态,同步状态为0时才能完全释放;读锁释放过程中不断释放写锁状态,直到为0,表示没有线程获取读锁。
参考文献
Java技术之AQS详解
Java并发-AQS及各种Lock锁的原理
Java多线程:AQS的更多相关文章
- Java多线程——AQS框架源码阅读
AQS,全称AbstractQueuedSynchronizer,是Concurrent包锁的核心,没有AQS就没有Java的Concurrent包.它到底是个什么,我们来看看源码的第一段注解是怎么说 ...
- Java多线程--AQS
ReentrantLock和AQS的关系 首先我们来看看,如果用java并发包下的ReentrantLock来加锁和释放锁,是个什么样的: 1 ReentrantLock reentrantLock ...
- Java多线程系列--AQS之 LockSupport
concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS(JAVA CAS原理.unsafe.AQS)框架借助于两个类: Unsafe(提供CAS操作 ...
- Java多线程并发06——CAS与AQS
在进行更近一步的了解Java锁的知识之前,我们需要先了解与锁有关的两个概念 CAS 与 AQS.关注我的公众号「Java面典」了解更多 Java 相关知识点. CAS(Compare And Swap ...
- Java多线程专题4: 锁的实现基础 AQS
合集目录 Java多线程专题4: 锁的实现基础 AQS 对 AQS(AbstractQueuedSynchronizer)的理解 Provides a framework for implementi ...
- 40个Java多线程问题总结
前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
- Java多线程系列--“JUC锁”04之 公平锁(二)
概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...
- Java多线程系列--“JUC锁”01之 框架
本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...
随机推荐
- OA系统高性能解决方案(史上最全的通达OA系统优化方案)
序: 这是一篇针对通达OA系统的整体优化方案,文档将硬件.网络.linux操作系统.程序本身(包括web和数据库)以及现有业务有效结合在一起,进行了系统的整合优化.该方案应用于真实生产环境,部署完成后 ...
- jquery里面的$.each()方法
$.each可以迭代jquery对象和数组 $(selector).each()专注于jquery对象的遍历
- Redhat5_linux 系统环境下 oracl11g的安装教程图解
linux_oracl11g 安装步骤 操作系统的安装敬请参考此文:VM 安装 linux Enterprise_R5_U4_Server_I386_DVD教程图解 设置linux服务器的静态地址请参 ...
- spark简单总结—短小精悍
Spark是基于内存计算的大数据并行计算框架.因为其基于内存计算,较Hadoop中MapReduce计算框架具有更高的实时性,同时保证了高效容错性和可伸缩性.从2009年诞生于AMPLab到现在已经成 ...
- RabbitMQ(一):RabbitMQ 安装与配置(Mac)
一.rabbitmq 安装与配置 安装: brew install rabbitmq # 进入安装目录 cd /usr/local/Cellar/rabbitmq/3.7.12 # 启动 brew s ...
- DDD领域模型查询方法实现(八)
在DDD.Domain工程文件夹Repository下创建RequestPage类: public class RequestPage { public RequestPage(int pagesiz ...
- oracle <> 选不出为null的部分
比如 tablea 的 字段b 为空,则 select * from tablea where b <> 'Y' 则查不出b is null 的部分
- [ZJOI2011]最小割
题解: 以前看过,思维挺神奇的一道题目 首先可以证明最小割是不能相交的 那么我们就可以找到任意两点求一次最小割然后将割的两边分开来再递归这个过程 另外最小割就是vis=0与vis=1之间的连边 分治的 ...
- sed & awk之sed
sed处理文本的方法 sed在处理文本时,会先读取第一个输入行,将编辑命令应用于输入行,然后读取下一个输入行,并应用编辑命令.sed总是处理最新版本的行,因此sed中有多个编辑命令时,编辑命令的顺序对 ...
- Windows 7下载安装 Visual C++ 6.0(VC6) 全程图解
说实话我也一直没有试过,所以也想当然的认为Win7下就不能安装VC6,压根就100%不兼容?一直使用高版本的VS(如VS2008和现在用的VS2010)的我今天亲身在Win7下安装一次试试. 注:文中 ...