AbstractQueuedSynchronizer

AbstractQueuedSynchronizer介绍

JDK1.5中提供的java.util.concurrent包中的大多数的同步器(Synchronizer)如Lock, Semaphore, Latch, Barrier等,这些类之间大多可以互相实现,如使用Lock实现一个Semaphore或者反过来,但是它们都是基于java.util.concurrent.locks.AbstractQueuedSynchronizer这个类的框架实现的

AbstractQueuedSynchronizer,队列同步器,简称AQS,它是java并发用来构建锁或者其他同步组件的基础框架。

一般使用AQS的主要方式是继承,子类通过实现它提供的抽象方法来管理同步状态,主要管理的方式是通过tryAcquiretryRelease类似的方法来操作状态.

AQS本身是没有实现任何同步接口的,它仅仅只是定义了同步状态的获取和释放的方法来供自定义的同步组件的使用。在java的同步组件中,AQS的子类一般是同步组件的静态内部类。同步组件是面向使用者的,它定义了使用者与组件交互的接口,隐藏了具体的实现细节;而AQS面向的是同步组件的实现者,它简化了具体的实现方式,屏蔽了线程切换相关底层操作.

AQS源码分析

AQS的实现依赖内部的同步队列(FIFO双向队列)来完成同步状态的管理,假如当前线程获取同步状态失败,AQS会将该线程以及等待状态等信息构造成一个Node,并将其加入同步队列,同时阻塞当前线程。当同步状态释放时,唤醒队列的首节点。

Node

Node主要包含以下成员变量:

1
2
3
4
5
6
7
8
class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
...
}
  • waitStatus:节点状态,主要有这几种状态:
    1. CANCELLED:当前线程等待已经取消,是唯一一个大于0的状态;
    2. SIGNAL:当前节点的后继节点需要运行;
    3. CONDITION:当前节点在等待condition;
    4. PROPAGATE:当前场景下后续的acquireShared可以执行;
  • prev:前驱节点;
  • next:后继节点;
  • thread:进入队列的当前线程;
  • nextWaiter:存储condition队列中的后继节点;

Nodesync队列和condition队列构建的基础,AQS拥有三个成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
  • 同步队列插入节点

当线程在获取锁的过程中被阻塞之后,这个线程会被包装成一个Node节点并被添加到线程同步队列之中,AQS提供基于CAS的设置尾节点的方法:

1
2
3
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

假如现在有多个线程同时被阻塞,那么各个线程所获取的尾节点就有可能相同,线程就不能被正确地添加到同步队列中去了,使用compareAndSetTail(Node expect,Node update)来对加入同步队列的线程进行安全地插入

img

  • 节点删除
    同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态之后将会唤醒后继节点,后继节点将会在获取同步状态成功的时候将自己设置为首节点。

    img

    设置首节点是由获取同步状态成功的线程来完成,因为每次只会有一个线程能够成功的获取到同步状态,所以,设置首节点并不需要CAS来保证。

热评文章