Java笔记 ·

并发学习笔记15-队列同步器

并发学习系列以阅读《Java并发编程的艺术》一书的笔记为蓝本,汇集一些阅读过程中找到的解惑资料而成。这是一个边看边写的系列,有兴趣的也可以先自行购买此书学习。
本文首发:windCoder.com

队列同步器AbstractQueuedSynchronizer(AQS)是用来构建锁或者其他同步组件的基础框架。该类也是其他许多同步类的基类,许多同步器通过AQS很容易构造出来(如,ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock等,Java6以前的版本还包含SynchronousQueue和FutureTask)。

AQS解决了在实现同步器时涉及的大佬细节问题,如获取同步状态,等待线程采用FIFO队列操作顺序。

基于AQS来构建同步器能带来许多好处。不仅能极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题(这是在没有使用AQS来构建同步器时的情况)。

基于AQS构建的同步器中,只可能在一个时刻发生非阻塞,从而降低了上下文切换的开销,并提高吞吐量。在设计AQS时充分考虑了可伸缩性,因此java.util.concurrent中所有基于AQS构建的同步器都能获得这个优势。

AQS使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作并发包的作者Doug Lea期望它能成为实现大部分同步需求的基础。

同步器主要使用方式是继承

子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中需要对同步状态进行改变,此时需要使用同步器提供的3个方法来进行操作:

  • 1.getState()
  • 2.setState(int newState)
  • 3.compareAndSetState(int expect, int update)

因为它们能保证状态的改变是线程安全的

子类推荐被定义为自定义同步组件的静态内部类同步器自身没有实现任何同步接口,它仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式的获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件。

同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以如下理解两者的关系:

  • 锁时面向使用者的,定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节。
  • 同步器面向的是锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。

队列同步器的接口

同步器的设计师基于模板方法模式的:

  • 使用者需要继承同步器并重写指定方法,
  • 随后将同步器组合在自定义同步组件的实现中,
  • 并调用同步器提供的模板方法,
  • 而这些模板方法会调用使用者重写的方法。

需要使用的同步器提供的方法

重写同步器指定的方法时,需要使用同步器提供的如下3个方法来访问或修改同步状态:

方法名 描述
getState() 获取当前同步状态。
setState(int newState) 设置当前同步状态。
compareAndSetState(int expect, int update) 使用CAS设置当前状态,该方法能够保证状态设置的原子性。

同步器可重写的方法

同步器可重写的方法与描述如下:

方法名 描述
protected boolean tryAcquire(int arg) 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态。
protected boolean tryRelease(int arg) 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态。
protected boolean tryAcquireShare(int arg) 共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败。
protected boolean tryReleaseShare(int arg) 共享式释放同步状态。
protected boolean isHeldExclusively() 当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所占用。

实现自定义同步组件时调用的模板方法

实现自定义同步组件时,将会调用同步器提供的模板方法,这些(部分)模板方法与描述如下:

方法名 描述
void acquire(int arg) 独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回。否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)方法。
void acquireInterruptibly(int arg) 与acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程中断,则该方法会抛出InterruptedException并返回。
boolean tryAcquireNanos(int arg, long nanos) 在acqureInterruptibly(int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,则将会返回false,已经获取则返回true。
void acquireShare(int arg) 共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态
void acquireShareInterruptibly(int arg) 共享式获取同步状态,该方法响应中断 。
boolean tryAcquireShareNanos(int arg, long nanos) 在acquireShareInterruptibly(int arg)基础上增加了超时限制。
boolean release(int arg) 独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒。
boolean releaseShare(int arg) 共享式的释放同步状态。
Collection getQueuedThreads() 获取等待在同步队列上的线程集合。

同步器提供的模板方法基本上分为3类:

  • 独占式获取同步状态
  • 分享式获取与释放同步状态
  • 查询同步队列中的等待线程情况

自定义同步组件将使用同步器提供的模板方法实现自己的同步语义。

参考资料

《Java并发编程的艺术》

《Java并发编程实战》

参与评论