AQS三大核心组成部分:同步状态state、CLH队列和ConditionObject;
状态
getState()
:返回同步状态
setState(int newState)
:设置同步状态
compareAndSetState(int expect, int update)
:使用CAS
设置同步状态
isHeldExclusively()
:当前线程是否持有资源
独占资源(不响应线程中断)
tryAcquire(int arg)
:独占式获取资源,子类实现
acquire(int arg)
:独占式获取资源模板
tryRelease(int arg)
:独占式释放资源,子类实现
release(int arg)
:独占式释放资源模板
共享资源(不响应线程中断)
tryAcquireShared(int arg)
:共享式获取资源,返回值大于等于0则表示获取成功,否则获取失败,子类实现
acquireShared(int arg)
:共享式获取资源模板
tryReleaseShared(int arg)
:共享式释放资源,子类实现
releaseShared(int arg)
:共享式释放资源模板
获取独占、共享资源操作还提供超时与响应中断的扩展函数;
- 同步状态state
state
的具体语义由实现者去定义;sdk现有的几个实现类:
-
ReentrantLock
的state
用来表示是否有锁资源 -
ReentrantReadWriteLock
的state
高16
位代表读锁状态,低16
位代表写锁状态 -
Semaphore
的state
用来表示可用信号的个数 -
CountDownLatch
的state
用来表示计数器的值
- CLH队列
CLH
是AQS
内部维护的FIFO
(先进先出)双端双向队列(方便尾部节点插入),基于链表数据结构,当一个线程竞争资源失败,就会将等待资源的线程封装成一个Node(内部也有一个state)
节点,通过CAS
原子操作插入队列尾部,最终不同的Node
节点连接组成了一个CLH
队列,所以说AQS
通过CLH
队列管理竞争资源的线程。有以下特点:
-
先进先出保证了公平性
-
非阻塞的队列,通过自旋锁和
C A S
保证节点插入和移除的原子性,实现无锁快速插入 -
采用了自旋锁思想,所以
CLH
也是一种基于链表的可扩展、高性能、公平的自旋锁
Node内部类
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// waitStatus的枚举值
static final int CANCELLED = 1;//表示当前节点被取消,当timeout或者被中断就会进入此状态
static final int SIGNAL = -1;//表示后继节点在等待当前节点唤醒,当后继结点进等待队列时,会将前置节点设置为此状态,由后节点来设置
static final int CONDITION = -2;//表示节点处于等待队列中,当其他节点调用了Condition的signal()方法后,处于CONDITION状态的Node会进入等待队列中
static final int PROPAGATE = -3;//共享模式下的节点状态,前置节点会唤醒后继节点,同时也可能会唤醒后继的后继节点
// 默认值:0,表示等待着获取锁,用volatile修饰,保证了可见性
volatile int waitStatus;
// 前置节点,入队时设置,节点移除时设置为NULL
volatile Node prev;
// 后置节点,入队时设置,节点移除时设置为NULL
volatile Node next;
// 把当前队列加入到队列的线程,入队时设置,节点移除时设置为NULL
volatile Thread thread;
// nextWaiter有:EXCLUSIVE、SHARED标识当前节点是独占模式还是共享模式;
// 与ConditionObject搭配使用作为条件等待队列节点时,nextWaiter保存后继节点。
// 所以实际上这个Node类是被复用了,既用于同步队列,也用于条件等待队列。
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;
}
//这个无参构造方法只用作创建head和SHARED对象
Node() {
}
//Used by addWaiter
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
//Used by Condition
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
- addWaiter(入队)
- acquire(获取资源、出队)
CLH
队列中的节点都是获取资源失败的线程节点,当持有资源的线程释放资源时,会将head.next
指向的线程节点唤醒(CLH
队列的第二个节点),如果唤醒的线程节点获取资源成功,线程节点清空信息设置为头部节点(新哨兵节点),原头部节点出队(原哨兵节点);
- 条件变量ConditionObject
Object
的wait、notify
函数是配合Synchronized
锁实现线程间同步协作的功能,AQS
的ConditionObject
条件变量也提供这样的功能,通过ConditionObject
的await
和signal
两类函数完成;
不同于Synchronized
锁,一个AQS
可以对应多个条件变量,而Synchronized
只有一个;
ConditionObject
内部维护着一个单向条件队列,不同于CHL
队列,条件队列只入队执行await
的线程节点,并且加入条件队列的节点,不能在CHL
队列, 条件队列出队的节点,会入队到CHL
队列。
当某个线程执行了ConditionObject
的await
函数,阻塞当前线程,线程会被封装成Node
节点添加到条件队列的末端,其他线程执行ConditionObject
的signal
函数,会将条件队列头部线程节点转移到CHL
队列参与竞争资源;
条件队列Node
类是使用nextWaiter
变量指向下个节点,并且因为是单向队列,所以prev
与next
变量都是null,这个上面也有提到;
- 独占式获取资源
acquire
是个模板函数,模板流程就是线程获取共享资源,如果获取资源成功,线程直接返回,否则进入CLH
队列,直到获取资源成功为止,且整个过程忽略中断的影响;
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//传入欲获取系统资源的节点
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//1.获取前置节点,如果是排在第一个的等待节点,它的前置节点应该是就是哨兵节点
final Node p = node.predecessor();
//2.获取资源成功,设置当前节点为头节点,清空当前节点的信息,把当前节点变成哨兵节点
if (p == head && tryAcquire(arg)) {
setHead(node);//获取资源成功,当前节点也就需要移除队列:将它作为信达哨兵节点放在head
p.next = null; // help GC, 原来的哨兵就被释放了
failed = false;
return interrupted;//返回中断状态
}
/**
* 如果前驱节点不是首节点,先执行shouldParkAfterFailedAcquire函数,shouldParkAfterFailedAcquire做了三件事
* 1.如果前驱节点的等待状态是SIGNAL,返回true,执行parkAndCheckInterrupt函数,返回false
* 2.如果前驱节点的等大状态是CANCELLED,把CANCELLED节点全部移出队列(条件节点)
* 3.以上两者都不符合,更新前驱节点的等待状态为SIGNAL,返回false
*/
if (shouldParkAfterFailedAcquire(p, node) &&
//使用LockSupport类的静态方法park挂起当前线程,直到被唤醒,唤醒后检查当前线程是否被中断,返回该线程中断状态并重置中断状态
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 尝试获取资源失败并执行异常,取消请求,将当前节点从队列中移除
if (failed)
cancelAcquire(node);
}
}
- 独占式释放资源
public final boolean release(int arg) {
if (tryRelease(arg)) {//释放资源成功,tryRelease子类实现
//获取头部线程节点
Node h = head;
if (h != null && h.waitStatus != 0) //头部线程节点不为null,并且等待状态不为0
//唤醒CHL队列第二个线程节点
unparkSuccessor(h);
return true;
}
return false;
}
//激活后继节点
private void unparkSuccessor(Node node) {
//获取节点等待状态
int ws = node.waitStatus;
if (ws < 0)
//cas更新节点状态为0
compareAndSetWaitStatus(node, ws, 0);
//获取下个线程节点
Node s = node.next;
if (s == null || s.waitStatus > 0) { //如果下个节点信息异常,从尾节点循环向前获取到正常的节点为止,正常情况不会执行
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//唤醒线程节点
LockSupport.unpark(s.thread);
}
}
- 共享式获取资源
获取资源成功,还会唤醒后续资源,因为资源数可能>0
,代表还有资源可获取,所以需要做后续线程节点的唤醒
public final void acquireShared(int arg) {
/**
* 1.负数表示失败
* 2.0表示成功,但没有剩余可用资源
* 3.正数表示成功且有剩余资源
*/
if (tryAcquireShared(arg) < 0) //获取资源失败,tryAcquireShared子类实现
//自旋阻塞等待获取资源
doAcquireShared(arg);
}
//类似acquireQueued
private void doAcquireShared(int arg) {
//根据当前线程创建出共享模式节点,加入搭配CLH队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//将自己设置未头节点,并且尝试唤醒后续系节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 共享式释放资源
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//释放资源成功,tryReleaseShared子类实现
//唤醒后继节点
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
//获取头节点
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {//如果头节点等待状态为SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//更新头节点等待状态为0
continue; // loop to recheck cases
//唤醒头节点下个线程节点
unparkSuccessor(h);
}
//如果后继节点暂时不需要被唤醒,更新头节点等待状态为PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
- 实例
package cyq.toolchain.AQS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* @author cyq
* @date 2021/5/8
* @desc talk is cheep,show me the code
*/
public class NonReentrantLock implements Lock {
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return false;
}
/**
* 获取锁-超时机制
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
//Aqs独占式-获取资源模板函数(超时机制)
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(0);
}
@Override
public Condition newCondition() {
return sync.createConditionObject();
}
//独占式不可重入锁
private static class Sync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
if (arg != 1) {
//获取锁操作,是需要把state更新为1,所以arg必须是1
throw new RuntimeException("arg not is 1");
}
if (compareAndSetState(0, arg)) {//cas 更新state为1成功,代表获取锁成功
//设置持有锁线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if (arg != 0) {
//释放锁操作,是需要把state更新为0,所以arg必须是0
throw new RuntimeException("arg not is 0");
}
//清空持有锁线程
setExclusiveOwnerThread(null);
//设置state状态为0,此处不用cas,因为只有获取锁成功的线程才会执行该函数,不需要考虑线程安全问题
setState(arg);
return true;
}
@Override
protected boolean isHeldExclusively() {
return super.getState() == 1;
}
/**
* 提供创建条件变量入口
*/
public ConditionObject createConditionObject() {
return new ConditionObject();
}
}
private static int j = 0;
public static void main(String[] args) throws InterruptedException {
NonReentrantLock nonReentrantLock = new NonReentrantLock();
Runnable runnable = () -> {
//获取锁
nonReentrantLock.lock();
for (int i = 0; i < 100000; i++) {
j++;
}
//释放锁
nonReentrantLock.unlock();
};
Thread thread = new Thread(runnable);
Thread threadTwo = new Thread(runnable);
thread.start();
threadTwo.start();
thread.join();
threadTwo.join();
System.out.println(j);
}
}