AQS AbstractQueuedSynchronizer

阅读数:71 评论数:0

跳转到新版页面

分类

python/Java

正文

一、概述

1、什么AQS

翻译过来是队列同步器,是用来构建锁或者其他同步组件的基础框架。

2、AQS使用的方式和其中的设计模式

AQS的主要使用方式是继承,子类通过继承AQS并实现它的抽象方法来管理同步状态。在AQS里由一个int型的state来代表这个状态,可以通过提供的三个方法来更改状态(getState()、setState(int newState)和compareAndSetState(int expect,int update))。

锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节。

同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒底层操作。

二、AQS结构

1、主要包含两部分内容:

(1)共享资源:是一个volatile的int类型变量。

(2)等待队列:是一个线程安全的队列,当线程拿不到锁时,会入队列。

2、核心思想

核心方法:

(1)acquire

// acquire操作
while (当前同步器的状态不允许获取操作) {
    如果当前线程不在队列中,则将其插入队列
    阻塞当前线程
}
如果线程位于队列中,则将其移出队列

(2)release

更新同步器的状态
if (新的状态允许某个被阻塞的线程获取成功)
    解除队列中一个或多个线程的阻塞状态

3、三个基本组件

(1)同步器状态的原子性管理

属性state被声明为volatile,并且通过使用CAS指令来实现compareAndSetState,使得当且仅当同步状态拥有一个一致的期望值的时候,才会被原子地设置成新值,这样就达到了同步状态的原子性管理,确保了同步状态的原子性、可见性和有序性。

(2)线程阻塞与解除阻塞

j.u.c.locks包提供了LockSupport类。

LockSupport.park阻塞当前线程直到有个LockSupport.unpark方法被调用。unpark的调用是没有被计数的,因此在一个park调用前多次调用unpark方法只会解除一个park操作。另外,它们作用于每个线程而不是每个同步器。一个线程在一个新的同步器上调用park操作可能会立即返回,因为在此之前可以有多余的unpark操作。但是,在缺少一个unpark操作时,下一次调用park就会阻塞。虽然可以显式地取消多余的unpark调用,但并不值得这样做。在需要的时候多次调用park会更高效。park方法同样支持可选的相对或绝对的超时设置,以及与JVM的Thread.interrupt结合 ,可通过中断来unpark一个线程。

(3)队列的管理

整个框架的核心就是如何管理线程阻塞队列,该队列是严格的FIFO队列,因此不支持线程优先级的同步。同步队列的最佳选择是自身没有使用底层锁来构造的非阻塞数据结构,业界主要有两种选择,一种是MCS锁,另一种是CLH锁。其中CLH一般用于自旋,但是相比MCS,CLH更容易实现取消和超时,所以同步队列选择了CLH作为实现的基础。

CLH队列实际并不那么像队列,它的出队和入队与实际的业务使用场景密切相关。它是一个链表队列,通过AQS的两个字段head(头节点)和tail(尾节点)来存取,这两个字段是volatile类型,初始化的时候都指向了一个空节点。

4、条件队列

队列的管理除了有同步队列,还有条件队列。AQS只有一个同步队列,但是可以有多个条件队列。AQS框架提供了一个ConditionObject类,给维护独占同步的类以及实现Lock接口的类使用。

ConditionObject类实现了Condition接口,Condition接口提供了类似Object管程式的方法,如await、signal和signalAll操作,还扩展了带有超时、检测和监控的方法。ConditionObject类有效地将条件与其它同步操作结合到了一起。该类只支持Java风格的管程访问规则,这些规则中,当且仅当当前线程持有锁且要操作的条件(condition)属于该锁时,条件操作才是合法的。这样,一个ConditionObject关联到一个ReentrantLock上就表现的跟内置的管程(通过Object.wait等)一样了。两者的不同仅仅在于方法的名称、额外的功能以及用户可以为每个锁声明多个条件。

ConditionObject类和AQS共用了内部节点,有自己单独的条件队列。signal操作是通过将节点从条件队列转移到同步队列中来实现的,没有必要在需要唤醒的线程重新获取到锁之前将其唤醒。

三、AQS的应用

1、ReetrantLock

ReentrantLock类使用AQS同步状态来保存锁重复持有的次数。当锁被一个线程获取时,ReentrantLock也会记录下当前获得锁的线程标识,以便检查是否是重复获取,以及当错误的线程试图进行解锁操作时检测是否存在非法状态异常。ReentrantLock也使用了AQS提供的ConditionObject,还向外暴露了其它监控和监测相关的方法。

2、ReetrantReadWriteLock

ReentrantReadWriteLock类使用AQS同步状态中的16位来保存写锁持有的次数,剩下的16位用来保存读锁的持有次数。WriteLock的构建方式同ReentrantLock。ReadLock则通过使用acquireShared方法来支持同时允许多个读线程。

3、Semaphore

Semaphore类(信号量)使用AQS同步状态来保存信号量的当前计数。它里面定义的acquireShared方法会减少计数,或当计数为非正值时阻塞线程;tryRelease方法会增加计数,在计数为正值时还要解除线程的阻塞。

4、CountDownLatch

CountDownLatch类使用AQS同步状态来表示计数。当该计数为0时,所有的acquire操作(对应到CountDownLatch中就是await方法)才能通过。

5、FutureTask

FutureTask类使用AQS同步状态来表示某个异步计算任务的运行状态(初始化、运行中、被取消和完成)。设置(FutureTask的set方法)或取消(FutureTask的cancel方法)一个FutureTask时会调用AQS的release操作,等待计算结果的线程的阻塞解除是通过AQS的acquire操作实现的。

6、SyncrhonousQueues

SynchronousQueues类使用了内部的等待节点,这些节点可以用于协调生产者和消费者。同时,它使用AQS同步状态来控制当某个消费者消费当前一项时,允许一个生产者继续生产,反之亦然。




相关推荐

一、创建线程 创建线程有四种方式:继承Thread类、实现Runnable接口、实现Callable接口、通过线程池创建。 1、继承Thread 重写run方法。 class A extends Th

FutureTask实现了RunnableFuture接口,同时RunnableFuture又继承了Future、Runnable接口。 通过传入Runnable或者Callable的任务给Futur

一、简介 分布式锁,其原理就是多台机器去争抢一个资源,谁争抢成功,那么就持有这把锁。 可以通过多种途径实现分布式锁,例如数据库,插入一条记录(唯一索引),谁插入成功,谁就持有;还可以通过zookeep

一、概述 1、cas理论 CompareAndSet (1)悲观锁 每一次访问都会加上锁。 (2)乐观锁 并不会每一次都为线程加上锁。 而CAS是一种乐观锁的实现。 2、AtomicReference

switch case支持的类型:byte、short、char、int、枚举类型、java.lang.String中。 一般出现上述问题,多半是因为在switch case语句中使用了基础类型的包装

一、可重入锁、不可重入锁 Java中提供的synchronized、ReentrantLock、ReenTrantReadWriteLock都是可重入锁(可重入:当前线程获取到A锁,在获取之后尝试再次

用法不同 synchronized 可以用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用于代码块。 获取锁和释放锁的机制不同 synchronized 是自动加锁和

一、概述 只要没有写入,读取锁可以由多个读线程同时保持,但写锁是独占的。 公平性 支持公平锁和非公平锁的获取,非公平锁的吞吐量优于公平锁,默认是非公平锁 可重入 线程获取读锁之后能哆再次获取