java多线程同步机制AQS解读
文章目录
一、简介
AQS(AbstractQueuedSynchronizer,同步器)是java中同步机制的基框架,jdk中的常用同步类如ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基于AQS同步器实现的,这里结合源码对AQS进行解读,加深对jdk同步类的理解。
二、AQS核心知识点
2.1 AQS同步器类定义
AQS即AbstractQueuedSynchronizer类,是一个抽象类,类定义如下:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//具体代码在此省略,关键代码后续介绍
}
其中继承的抽象类AbstractOwnableSynchronizer源码如下:
package java.util.concurrent.locks;
//一个同步器可能是被一个线程独立占有的, AbstractOwnableSynchronizer提供了创建锁和相关同步器的基本定义。
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 3737899427754241961L;
//空构造器
protected AbstractOwnableSynchronizer() { }
//独占模式下的拥有者
private transient Thread exclusiveOwnerThread;
//设置指定线程拥有独立访问权限
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
//返回拥有独立访问权限的线程,没有则返回null
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
AbstractOwnableSynchronizer中的方法在独占模式下,当同步状态获取成功时会被调用。
2.2 核心实现
AQS同步器是基于FIFO(first-in-first-out)同步等待队列和同步状态(单个原子int变量)实现的,注意它没有实现任何同步接口。AQS有独占式和共享式两种模式,独占式同一时刻只能一个线程获取锁,而共享式同一时刻可以有多个线程获取锁。同步状态获取成功时,直接返回,否则线程进入同步队列,等待下次尝试获取。
2.2.1 FIFO同步队列
2.2.1.1 同步队列解释
同步队列是一个双向链表,也叫CHL(Craig、Landin、Hagersten)队列,由结点Node构成。当线程获取同步状态失败时,同步器将线程及等待状态等信息封装在一个Node结点里,并插入到同步队列尾部,同时线程阻塞。当同步状态释放时,同步器会唤醒同步队列首结点,让其参与尝试获取同步状态。同步器有head结点和tail结点,关联着同步队列,结构示意如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VnHOCdNH-1581082154873)(http://www.plantuml.com/plantuml/png/IojAp4rLU3gXvzdQfKzdhgZcKW22w8pKn9HO2BCaCJCdbgkMYoiDIUNB6VEVTao7A8Q8Fzyz-NdJJaE845XZGL5gcM4iWf-NMb42vTTYQ3H2qsY4fj48bqPZD8t6Q1gDOHfhkK2UKj3LjOEvbGlaDIG1Oouki1kHX8p08aQeYi3kGp48R36ADW00)]
2.2.1.2 同步队列源码
Node结点源码如下:
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import sun.misc.Unsafe;
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {
//共享模式下的Node结点
static final Node SHARED = new Node();
//独占模式下的Node结点
static final Node EXCLUSIVE = null;
//waitStatus值,线程处于取消状态,因同步队列中等待线程超时或被中断,将从同步队列中取消等待
static final int CANCELLED = 1;
//waitStatus值,当前结点获取同步状态,后继结点处于等待状态,如果当前结点线程释放同步状态或取消,则后继结点运行
static final int SIGNAL = -1;
//waitStatus值,结点在等待队列中,当其它线程调用Condition的signal()方法后,结点由等待队列转到同步队列,参与同步状态获取
static final int CONDITION = -2;
//waitStatus值,表示下一次同步状态获取将无条件传播下去
static final int PROPAGATE = -3;
//结点等待状态
volatile int waitStatus;
//前驱结点
volatile Node prev;
//后继结点
volatile Node next;
//结点中的线程
volatile Thread thread;
//等待队列中的后继结点(处于condition或共享状态,共享模式时值为SHARED),
Node nextWaiter;
//等待结点是否是共享模式下的等待
final boolean isShared() {
return nextWaiter == SHARED;
}
//获取前驱结点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
//初始化构造器
Node() { // Used to establish initial head or SHARED marker
}
//添加等待结点构造器
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
//添加结点构造器
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
}
同步队列的尾结点添加是基于CAS实现的,依赖方法enq、compareAndSetHead、compareAndSetTail实现,源码如下:
//添加结点入同步队列
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//cas方式设置同步队列头结点
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
//cas方式添加同步队列尾结点
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
2.2.1.3 同步队列安全保证
同步队列采用双向链表结构,结合CAS机制,保证了多线程下的安全,具体如下:
- 获取时,由于队首只有一个,多线程下,每次也只能取第一个;
- 插入时,采用CAS机制,只有在前一个结点确定的情况下才插入,从而保证插入是安全的;
2.2.2 同步状态
2.2.2.1 同步状态源码
同步器状态state管理的核心方法有getState、setState、compareAndSetState,源码及分析如下:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//等待队列头结点,延迟加载,如果head结点存在,则它的waitStatus值不能是CANCELLED
private transient volatile Node head;
//等待队列尾结点,延迟加载,只能通过enq方法添加新的等待结点
private transient volatile Node tail;
//同步状态
private volatile int state;
//获取同步器当前同步状态
protected final int getState() {
return state;
}
//设置同步器当前状态
protected final void setState(int newState) {
state = newState;
}
//原子的设置同步状态,如果当前值等于期望值,则更新新值
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
2.2.2.2 同步状态安全保证
同步状态使用int字段,添加volatile修饰,保证有序性和可见性,结合CAS机制,确保多线程下是安全的。
2.2.2.3 同步状态使用
同步状态是同步类的基础状态,对于共享模式和独占模式值的使用不一样:
- 独占模式:同一时刻只有一个线程占有,1表示占有成功,0表示占有失败;
- 共享模式:同一时刻可多个线程占有,大于0表示还可分配(可占有),小于等于0表示不可分配(占有失败);
2.3 同步实现
同步器使用了模板方法模式,子类通过继承AQS,重写受保护的改变同步状态的方法,进而实现所有的队列和阻塞机制,子类需实现的方法有tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively,源码定义如下:
//独占模式下尝试获取同步状态
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//独占模式下尝试释放同步状态
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//共享模式下尝试获取同步状态
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
//独占模式下尝试释放同步状态
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
//同步器是否被当前线程独占模式占有
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
2.4 使用方式
AQS使用了模板方法模式,子类通过继承AQS,重写受保护的改变同步状态的方法,进而实现所有的队列和阻塞机制。同时子类应该是同步类内部的非public的帮助类,用于实现同步类的同步特性。
三、示例
这里以ReentrantLock加锁为例,走下整个流程。
3.1 调用示例
ReentrantLock加锁调用代码很简单,如下:
Lock lock = new ReentrantLock();
lock.lock();
3.2 调用流程
四、备注
这里介绍AQS同步器的关键代码、说明及示例,更多详情请查看jdk相关源码。