JAVA REENTRANTLOCK、SEMAPHORE 的实现与 AQS 框架
引言
ReentrantLock是JDK提供的一个可重入互斥锁,所谓可重入就是同一个锁允许被已经获得该锁的线程重新获得。可重入锁的好处可以在递归算法中使用锁,不可重入锁则导致无法在递归算法中使用锁。因为第二次递归时由于第一次递归已经占有锁,而导致死锁。本文我们将探讨JDK中ReentrantLock的实现。
Semaphore是JDK提供的一个可共享的同步组建,有n个许可,多个线程可以共同去获得许可,当线程申请的许可小于n时即可成功申请,否则申请失败。
AQS(AbstractQueuedSynchronizer)是Java实现同步组建的基础框架,一般以静态内部类的形式实现在某个同步组件类中,通过代理的方式向外提供同步服务,ReentrantLock和Semaphore都是基于AQS实现的同步组件,前者是独占式同步组建,即一个线程获得后,其他线程无法获得。后者是共享式同步组件,一个线程获得后,在满足的条件下,其他线程也可以获得。
AQS工作原理
AQS是Java实现同步组建的基础框架,其基本思想是用一个volatile int state变量来表示当前同步组件的状态,用getState()获取同步组件的状态,用compareAndSet(int expect, int update)来对state状态进行操作,compareAndSet可以保证对state变量更新值的原子性。AQS中很多方法是final的,即不允许用户覆盖,用户自定义的方法一般有:
- tryAcquire: 独占式获取同步状态,该函数一般首先查询state的值,如果state不允许继续被获取,直接返回false。如果state允许继续被获取,CAS尝试更新state的值,成功返回true,失败返回false
- tryAcquireShared:共享式的获取同步状态,该一般是在CAS死循环获取state的值,计算state被获取后的值,如果该值为负数,直接返回负数表示失败,如果该值为正值,则用CAS更新该值,当CAS更新失败时,重复上述步骤,直至返回负数或CAS更新成功返回正值。
- tryRelease:独占式的释放同步状态
- tryReleaseShared:共享式的释放同步状态,一般在CAS死循环中反复尝试,直至释放成功
- isHeldExclusively:判断当前同步器是否被当前线程占有
AQS提供的模板方法有:
- acquire:独占式的获取同步状态,获取成功则返回,获取失败则会进入等待队列,该方法会调用用户自定义的tryAcquire函数
- acquireInterruptibly:与acquire类似,不同在于当进入等待队列时,遇到中断会抛出InterruptedException异常,用户可以处理该中断异常
- tryAcquireNanos:在acquireInterruptibly的基础上增加了时间限制,一定时间内没有成功获取则返回false
- acquireShared:共享式的获取同步状态,成功则返回,失败则进入等待队列,该方法会调用用户自定义的tryAcquired函数
- acquireSharedInterruptibly:在等待队列可以相应中断,与上类似
- tryAcquireShared:在acquireSharedInterruptibly增加了超时限制
- release:独占式的释放同步状态,会调用用户自定义tryRelease函数
- releaseShared:共享式的释放同步状态,会调用用户自定义tryReleaseShared函数
- getQueuedThreads:获取等待队列线程集合
ReentrantLock源码分析
ReentrantLock的默认构造函数是
1
2
3
|
public ReentrantLock() { sync = new NonfairSync(); } |
NonfairSync继承了Sync,Sync是一个抽象类,并继承了抽象类AbstractQueuedSynchronizer。
ReentrantLock是一个独占式的锁,所以它需要实现tryAcquire函数和tryRelease函数
tryAcquire函数源码如下
1
2
3
|
protected final boolean tryAcquire( int acquires) { return nonfairTryAcquire(acquires); } |
nonfairTryAcquire(acquires)源码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
final boolean nonfairTryAcquire( int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0 ) { if (compareAndSetState( 0 , acquires)) { 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 ; } |
- 先得到当前线程
- 查询当前state值,如果为0则说明当前锁还未被其他线程获取,则尝试CAS获得锁,成功则把占有锁的线程设置为当前线程,返回true。失败返回false。
- 如果state不为0则说明该锁已经被其他线程获取,则检查获得锁的线程是否是当前线程以实现可重入特性,如果是,则更新state的值,并返回true。此处更新不需要CAS,因为只有当前线程可以操作state。
- 其他情况返回false
tryRelease函数源码如下
1
2
3
4
5
6
7
8
9
10
11
12
|
protected final boolean tryRelease( int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false ; if (c == 0 ) { free = true ; setExclusiveOwnerThread( null ); } setState(c); return free; } |
- 首先得到释放之后的状态值c
- 检查当前释放锁的线程,如果不是已占有锁的线程则抛出异常,因为ReentrantLock是独占式锁,释放锁的线程一定是占有锁的线程
- 如果c是等于0的,说明获取锁的所有函数都已经返回,则锁释放成功
- 如果c不等于0,说明只是部分递归的函数返回,部分递归函数还未返回,则释放失败,锁依然被占有
Lock函数源码
1
2
3
|
public void lock() { sync.lock(); } |
sunc的lock函数
1
2
3
4
5
6
|
final void lock() { if (compareAndSetState( 0 , 1 )) setExclusiveOwnerThread(Thread.currentThread()); else acquire( 1 ); } |
该函数首先直接尝试CAS操作,成功则设置当前函数为占有锁的函数,返回,失败则调用acquire函数。acquire函数为AQS实现的模板方法,它尝试获得锁,成功则返回,不成功则进入等待队列直至获取成功。
unLock函数源码
1
2
3
|
public void unlock() { sync.release( 1 ); } |
调用tryRelease函数释放锁。
Semaphore的源码
Semaphore构造函数如下:
1
2
3
|
public Semaphore( int permits) { sync = new NonfairSync(permits); } |
与ReentrantLock代码结构非常相似。Semaphore是一个共享式的同步组建,它应该实现tryAcquireShared和tryReleaseShared
tryAcquireShared函数源码:
1
2
3
|
protected int tryAcquireShared( int acquires) { return nonfairTryAcquireShared(acquires); } |
nonfairTryAcquireShared源码:
1
2
3
4
5
6
7
8
9
|
final int nonfairTryAcquireShared( int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } |
- 函数首先取得当前的可用许可数,并计算被获取acquires个许可后剩余的许可数。
- 如果剩余的许可数小于0直接返回剩余的许可数,即负值
- 如果大于0则尝试使用CAS循环更新state的值,更新失败则重试上述步骤,直至返回负值更新失败,或者返回非负值更新成功。
tips:与独占式的tryAcquire逻辑不太一样,独占式的tryAcquire在CAS操作失败后,直接返回失败。本人觉得共享式的tryAcquiredShared在CAS操作失败后,因为组件是共享的,所以再次尝试获取同步组件成功的可能性较大,所以在CAS失败后,尝试再次更新。而独占式的CAS更新失败后,组件已经被其他线程获取,再次尝试成功的可能性较小,所以没有重新尝试。纯属个人观点。
tryReleasedShared源码
1
2
3
4
5
6
7
8
9
10
|
protected final boolean tryReleaseShared( int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error( "Maximum permit count exceeded" ); if (compareAndSetState(current, next)) return true ; } } |
- 函数计算释放后的state值并验证是否溢出
- CAS更新state的值直至成功
acquire函数源码
1
2
3
|
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly( 1 ); } |
acquireSharedInterruptibly为AQS提供的模板方法,调用了tryAcquireShared,成功直接返回,不成功加入等待队列,并添加了处理中断的机制
release函数源码
1
2
3
|
public void release() { sync.releaseShared( 1 ); } |
releaseShared为AQS提供的模板方法,调用了tryReleaseShared
总结
完整的ReentrantLock和Semaphore实现非常复杂,本文旨在介绍AQS框架,并通过ReentrantLock和Semaphore一个独占式的同步组件和一个非独占式的同步组件来学习怎么使用AQS实现通组件,具体来说分为以下步骤:
- 待实现的同步组件是独占式的还是共享式的
- 独占式的同步组件实现tryAcquire和tryRelease,非独占式的实现tryAcquireShared和tryReleaseShared
- 将我们实现的同步组建相应的方法如Lock和unLock代理到AQS对应的函数包括用户自定义的函数和AQS提供的模板函数
AQS的方便之处在于我们只需要实现tryAcquire和tryRelease或tryAcquireShared和tryReleaseShared就可以使用AQS帮我们实现好的阻塞的acquire函数,可中断的acquire函数,带超时的acquire函数等模板函数,大大简化了用户的开发量和难度。
JAVA REENTRANTLOCK、SEMAPHORE 的实现与 AQS 框架的更多相关文章
- Java并发包源码学习之AQS框架(一)概述
AQS其实就是java.util.concurrent.locks.AbstractQueuedSynchronizer这个类. 阅读Java的并发包源码你会发现这个类是整个java.util.con ...
- java并发包分析之———AQS框架
一.什么是同步器 多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足 xxxx 条件,才能触发线程执行 xxxx . 这个共同的语义可以称之为同步器.可以认为以上所有的锁机制都可以基 ...
- 《The java.util.concurrent Synchronizer Framework》 JUC同步器框架(AQS框架)原文翻译
一.论文简介 闲来无事,看看源码,发现了一篇JDK作者的论文<The java.util.concurrent Synchronizer Framework>主要描述了作者对Abstrac ...
- 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念
深入理解Java并发框架AQS系列(一):线程 深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念 一.AQS框架简介 AQS诞生于Jdk1.5,在当时低效且功能单一的synchroni ...
- Java并发包源码学习之AQS框架(四)AbstractQueuedSynchronizer源码分析
经过前面几篇文章的铺垫,今天我们终于要看看AQS的庐山真面目了,建议第一次看AbstractQueuedSynchronizer 类源码的朋友可以先看下我前面几篇文章: <Java并发包源码学习 ...
- Java并发包源码学习之AQS框架(三)LockSupport和interrupt
接着上一篇文章今天我们来介绍下LockSupport和Java中线程的中断(interrupt). 其实除了LockSupport,Java之初就有Object对象的wait和notify方法可以实现 ...
- Java并发框架——什么是AQS框架
什么是AQS框架 1995年sun公司发布了第一个java语言版本,可以说从jdk1.1到jdk1.4期间java的使用主要是在移动应用和中小型企业应用中,在此类领域中基本不用设计大型并发场景,当然也 ...
- Java多线程——AQS框架源码阅读
AQS,全称AbstractQueuedSynchronizer,是Concurrent包锁的核心,没有AQS就没有Java的Concurrent包.它到底是个什么,我们来看看源码的第一段注解是怎么说 ...
- Java并发包源码学习之AQS框架(二)CLH lock queue和自旋锁
上一篇文章提到AQS是基于CLH lock queue,那么什么是CLH lock queue,说复杂很复杂说简单也简单, 所谓大道至简: CLH lock queue其实就是一个FIFO的队列,队列 ...
随机推荐
- Qt学习资料
网址:http://www.qter.org/portal.php?mod=list&catid=18 qt开源社区 (门户)里面有在线学习资料(讲的比较粗略 但是进程比较快 适用于快速学习) ...
- Python学习笔记-chapter1
我自幼时自觉聪慧,但实缺恒力,遂二十余岁却一事无成,亦无一技傍身,实属惭愧. 少时便仰慕于新兴世界之IT技术,然因惰性,未曾一日习学. 今陷此困境,聊以度日,反无端生出些许时间,便志要潜心研学,不求能 ...
- MYSQL常用命令2
mysql 的dos命令行大全 2016年11月04日 16:03:59 阅读数:7987 1.连接Mysql(中文乱码在文章的最后) 格式: mysql -h主机地址 -u用户名 -p用户密码 1. ...
- getRealPath()和getContextPath()的区别
转载自:http://sucre.iteye.com/blog/319178 在程序中常常要获取文件的路径,有的时候需要用到相对路径而有的时候就要用到绝对路径,一提到绝对路径大家一定想到了getRea ...
- 爬虫模块介绍--selenium (浏览器自动化测试工具,模拟可以调用浏览器模拟人操作浏览器)
selenium主要的用途就是控制浏览器,模仿真人操作浏览器的行为 模块安装:pip3 install selenium 需要控制的浏览器 from selenium import webdriver ...
- IT名词概括与简单了解
云计算概念 云架构 我看过两本云计算,<云计算><云计算架构技术与实践> 云计算是一个很广的概念,简单的说将互联网中的计算机资源按需分配,提高闲置资源的利用率,需要多少你就购买 ...
- EF框架引用问题
安装EF框架时,从NuGet上安装 EF 安装完成以后仍然报错误 这个错误 是因为EF实体数据模型未引用System.data.entity 这个DLL ,记一下以防止以后忘记
- edgedb 内部pg 数据存储的探索 (二) 创建数据库命令说明
前面已经创建好了一个简单可以访问pg 的edgedb 环境,现在测试几个数据库操作命令在pg 的存储 创建数据库 连接环境 注意账户是按照上次创建的环境配置 edgedb -u edgedb 创建数据 ...
- 提交代码,SVN被锁定,提示:svn is already locked解决方案
今天遇到一个问题,svn 在提交代码的时候出现了svn is already locked,这是什么鬼,svn 没少用,但是这种情况,还是第一次遇到,于是,百度一下,查了一下,做一下个人分享. 遇到这 ...
- Linux ①
目录 一.计算机简单认识 二.linux初始 三.安装虚拟机 四.安装Linux系统 五. 使用xshell 6 进行远程控制 六.linux文档与目录结构 七.常用命令 一.计算机简单认识 1.服务 ...