youyichannel

志于道,据于德,依于仁,游于艺!

0%

Lock全解读02

队列同步器 —— AQS底层原理

队列同步器AbstractQueuedSynchronizer是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量state表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。它是大部分同步需求实现的基础,也是JUC的重要组件。

同步器的主要使用方式是继承,之类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的三个方法(getState()setState(int newState)compareAndSetState(int expect, int update))来进行操作,因为它们能够保证状态的改变是安全的。

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

同步器是实现锁和任意同步组件的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。

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

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

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

同步器中可以重写的方法:

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

同步器依赖内部的同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造为一个节点并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

节点是构成同步队列(等待队列)的基础,同步器拥有首节点和尾节点,没有成功获取同步状态的线程将会成为节点加入该队列的尾部,同步队列的基本结构:

同步器包含了两个节点类型的引用,一个指向头节点,而另一个指向尾节点。

当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取到同步状态,转而被构造称为节点并加入到同步队列中,而这个加入队列的过程必须要保证线程安全,因此同步器提供了一个基于CAS的设置尾节点的方法compareAndSetTail(Node expect, Node update),只有设置成功后,当前节点才正式与之前的尾节点建立关联。

「设置尾节点」

设置首节点是通过同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头节点的方法并不需要使用CAS来保证,它只需要将首节点设置为原首节点的后继节点并断开原首节点的next引用即可。

「设置首节点」

资料:https://www.bilibili.com/video/BV1qa4y1U7QZ/ 强推 河北王校长