前言

从AQS到不同锁的实现,jdk提供了比较强大的实现。AQS作为处理线程共享数据时的一种同步器,能够保障共享数据的有序访问。下面几篇系列文章从使用和源码的角度进行分析学习。


系列文章
一、并发编程系列-同步器 AQS
二、并发编程系列-同步器实现一 ReentrantLock
三、并发编程系列-同步器实现二 ReentrantLock Condition
四、并发编程系列-同步器实现三 CountDownLatch
五、并发编程系列-同步器实现四 Semaphore
六、并发编程系列-同步器实现五 CyclicBarrier


> 这篇文章主要先熟悉的AQS同步器

什么是AQS

首先AQS的全称:AbstractQueuedSynchronizer,叫做抽象队列同步器

从名字可以得到几个关键信息:
1、它是一个抽象类
2、它应该是基于队列实现的(队列特性 FIFO)
3、可以解决线程同步问题

能力

1、包括两个队列
一个是同步队列,属于双向队列
一个是条件队列,属于单向队列


双向队列

通过同步队列完成当前节点的状态记录,同时每个节点还对应一个线程对象,可以理解为 每个节点都代表一个线程

节点的加入,表示线程需要等待获取锁资源,入队列;
节点的退出,表示线程获取到锁资源,出队列进行任务执行
具备队列的特点:FIFO

2、一个状态标识
volatile int state;

是一个整数类型:
0表示资源没有被占用
1表示资源被使用了(具体表示全部还是1个,需要具体实现来定义)
大于1表示本身具有多个竞争资源或重复获取到资源进行了加1

state的更新是基于cas更新的,所以定义了volatile,保障可见性和有序性。

3、实现了锁等待和锁恢复的“原子”能力:
竞争资源 -> 进入队列 -> 睡眠 -> 唤醒 -> 竞争资源(成功的话) -> 出队列 -> 执行任务

资源的状态state维护交由子类实现,由子类通过state判断是否获取锁及释放锁释放成功,因为state代表一种资源,具体的资源分配情况由具体的实现操作。

结构如何定义

数据结构

因为是基于队列,自然链表是最好的实现方式,上代码

// 静态内部类,作为数据节点
static final class Node {
	// 去掉了英文注释,可以到源码中查看

	// 表明是一个共享锁
        static final Node SHARED = new Node();
        
	// 表明是一个排它锁节点
        static final Node EXCLUSIVE = null;

	// 节点状态,表明当前线程已被取消
        static final int CANCELLED =  1;

	// 节点状态,表明下一个节点需要当前节点唤醒,这样下个节点便可以安心睡眠了
        static final int SIGNAL    = -1;
        
	// 节点状态,表明线程在等待条件,条件队列才用的上
        static final int CONDITION = -2;

	// 表明下一个共享节点应该被无条件传播,当需要唤醒下一个共享节点时,会一直传播唤醒下一个直到非共享阶段
        static final int PROPAGATE = -3;
	// 还有个节点状态为0,刚竞争资源进入队列的时候的初始状态

	// 上面的节点状态均针对当前变量,通过cas更新
	volatile int waitStatus;
	
	// 前继节点
	volatile Node prev;
	// 后继节点
	volatile Node next;
	// 节点对应的线程信息
	volatile Thread thread;
	
	// 下一个等待节点,在条件队列中使用;同时也用来区分是排它锁节点还是共享锁节点的标识
	Node nextWaiter;

	//// 此处省略了一些构造函数
}

核心变量

// 等待(双向)队列的头节点
private transient volatile Node head;

// 等待(双向)队列的尾节点
private transient volatile Node tail;

// 同步器状态
private volatile int state;

// 继承的变量,排它模式下同步的持有者线程
private transient Thread exclusiveOwnerThread

/*
还有些偏移量值,用于cas时获取对应变量的内存地址偏移量
如:stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
cas:unsafe.compareAndSwapInt(this, stateOffset, expect, update);
*/
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;

核心方法

// 获取排它锁
public final void acquire(int arg) {}
// 注意:由子类实现,通过state控制尝试获取锁
protected boolean tryAcquire(int arg) {}
// 释放排它锁
public final boolean release(int arg) {}
// 注意:由子类实现,通过维护state尝试释放锁
protected boolean tryRelease(int arg) {}

// 获取共享锁
public final void acquireShared(int arg) {}
// 注意:由子类实现,通过维护state尝试获取锁
protected int tryAcquireShared(int arg) {}
// 释放共享锁
public final boolean releaseShared(int arg) {}
// 注意:由子类实现,通过state控制尝试释放锁
protected boolean tryReleaseShared(int arg) {}

还有一些其它方法:
如可中断式获取锁、超时获取锁(到点中断)

另一个内部类

用于条件队列

public class ConditionObject implements Condition{
	// 条件队列第一个节点
   	private transient Node firstWaiter;
	// 条件队列最后一个节点
        private transient Node lastWaiter;
	//注意:上文提到Node结构中有一个nextWaiter节点,一个使用场景便是条件队列的下一个节点(单链表结构)。
	
	// 当前线程等待,进入条件队列;类似于Object的wait(), 都需要获取到锁后执行
	public final void await(){}
	public final long awaitNanos(long nanosTimeout){}
	// 注意:唤醒基于当前条件等待的一个线程,加入到同步队列中,等待获取锁资源;类似于Object的notify(), 都需要获取到锁后执行
	public final void signal() {}
	// 唤醒所有条件等待线程,加入到同步队列中。
	public final void signalAll() {}
}

signal()唤醒基于当前条件等待的一个节点线程,从第一个开始,加入到同步队列队尾中,等待获取锁资源。

最后

对AQS有了大致的了解后,接下来分享不同的子类实现。

AQS方法的具体源码解读,会在实现的子类中分析,有上下文会有助于理解。