您的位置:首页 > 产品设计 > UI/UE

AbstractQueuedSynchronizer源码解读

2019-02-18 18:53 113 查看

文章出处 https://www.geek-share.com/detail/2707483520.html

1. 背景

AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)是Doug Lea大师创作的用来构建锁或者其他同步组件(信号量、事件等)的基础框架类。JDK中许多并发工具类的内部实现都依赖于AQS,如ReentrantLock, Semaphore, CountDownLatch等等。学习AQS的使用与源码实现对深入理解concurrent包中的类有很大的帮助。
本文重点介绍AQS中的基本实现思路,包括独占锁、共享锁的获取和释放实现原理和一些代码细节。

对于AQS中ConditionObject的相关实现,可以参考我的另一篇博文AbstractQueuedSynchronizer源码解读--续篇之Condition

2. 简介

AQS的主要使用方式是继承它作为一个内部辅助类实现同步原语,它可以简化你的并发工具的内部实现,屏蔽同步状态管理、线程的排队、等待与唤醒等底层操作。

AQS设计基于模板方法模式,开发者需要继承同步器并且重写指定的方法,将其组合在并发组件的实现中,调用同步器的模板方法,模板方法会调用使用者重写的方法。

3. 实现思路

下面介绍下AQS具体实现的大致思路。

AQS内部维护一个CLH队列来管理锁。
线程会首先尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个Node节点加到同步队列里。
接着会不断循环尝试获取锁(条件是当前节点为head的直接后继才会尝试),如果失败则会阻塞自己,直至被唤醒;
而当持有锁的线程释放锁时,会唤醒队列中的后继线程。

下面列举JDK中几种常见使用了AQS的同步组件:

  • ReentrantLock: 使用了AQS的独占获取和释放,用state变量记录某个线程获取独占锁的次数,获取锁时+1,释放锁时-1,在获取时会校验线程是否可以获取锁。
  • Semaphore: 使用了AQS的共享获取和释放,用state变量作为计数器,只有在大于0时允许线程进入。获取锁时-1,释放锁时+1。
  • CountDownLatch: 使用了AQS的共享获取和释放,用state变量作为计数器,在初始化时指定。只要state还大于0,获取共享锁会因为失败而阻塞,直到计数器的值为0时,共享锁才允许获取,所有等待线程会被逐一唤醒。

3.1 如何获取锁

获取锁的思路很直接:

[code]while (不满足获取锁的条件) {
把当前线程包装成节点插入同步队列
if (需要阻塞当前线程)
阻塞当前线程直至被唤醒
}
将当前线程从同步队列中移除

以上是一个很简单的获取锁的伪代码流程,AQS的具体实现比这个复杂一些,也稍有不同,但思想上是与上述伪代码契合的。
通过循环检测是否能够获取到锁,如果不满足,则可能会被阻塞,直至被唤醒。

3.2 如何释放锁

释放锁的过程设计修改同步状态,以及唤醒后继等待线程:

[code]修改同步状态
if (修改后的状态允许其他线程获取到锁)
唤醒后继线程

这只是很简略的释放锁的伪代码示意,AQS具体实现中能看到这个简单的流程模型。

3.3 API简介

通过上面的AQS大体思路分析,我们可以看到,AQS主要做了三件事情

  • 同步状态的管理
  • 线程的阻塞和唤醒
  • 同步队列的维护

下面三个protected final方法是AQS中用来访问/修改同步状态的方法:

  • int getState(): 获取同步状态

  • void setState(): 设置同步状态

  • boolean compareAndSetState(int expect, int update):基于CAS,原子设置当前状态

在自定义基于AQS的同步工具时,我们可以选择覆盖实现以下几个方法来实现同步状态的管理:

方法 描述
boolean tryAcquire(int arg) 试获取独占锁
boolean tryRelease(int arg) 试释放独占锁
int tryAcquireShared(int arg) 试获取共享锁
boolean tryReleaseShared(int arg) 试释放共享锁
boolean isHeldExclusively() 当前线程是否获得了独占锁

以上的几个试获取/释放锁的方法的具体实现应当是无阻塞的。

AQS本身将同步状态的管理用模板方法模式都封装好了,以下列举了AQS中的一些模板方法:

方法 描述
void acquire(int arg) 获取独占锁。会调用
tryAcquire
方法,如果未获取成功,则会进入同步队列等待
void acquireInterruptibly(int arg) 响应中断版本的
acquire
boolean tryAcquireNanos(int arg,long nanos) 响应中断+带超时版本的
acquire
void acquireShared(int arg) 获取共享锁。会调用
tryAcquireShared
方法
void acquireSharedInterruptibly(int arg) 响应中断版本的
acquireShared
boolean tryAcquireSharedNanos(int arg,long nanos) 响应中断+带超时版本的
acquireShared
boolean release(int arg) 释放独占锁
boolean releaseShared(int arg) 释放共享锁
Collection getQueuedThreads() 获取同步队列上的线程集合

上面看上去很多方法,其实从语义上来区分就是获取和释放,从模式上区分就是独占式和共享式,从中断相应上来看就是支持和不支持。

 

源码解读  请参考https://www.geek-share.com/detail/2707483520.html

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: