AQS
队列同步器AbstractQueuedSynchronizer是用来构建锁或其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
AQS的主要使用方式是继承,子类通过继承AQS并实现它的抽象方法来管理同步状态。
在AQS里由一个int型的state来代表这个状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时需要使用同步器提供的3个方法(getState()、setState(int newState)、compareAndSetState(int expect, int update))来进行操作。它们能够保证状态的改变是安全的。
AQS没有实现同步接口,它只是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器可以支持独占和共享式地获取同步状态,这样以来可以方便实现不同类型的同步组件,如TeentrantLock、ReentrantReadWriteLock、CountDownLatch等。
同步器是实现锁或任意同步组件的关键,在锁的实现中聚合同步器。它是面向锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。
锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节。
同步器和锁很好的隔离了实现者和使用者所需要关注的领域。实现者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,然后调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。
AQS的模板方法
模板方法模式:定义一个操作算法的骨架,而将一些步骤的实现延迟到子类中。模板方式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。比如Spring框架里的Template。
AQS提供的模板方法大致分为3类:独占式获取/释放同步状态、共享式获取/释放同步状态、查询同步队列中等待线程的情况。
方法名称 | 说 明 |
---|---|
void acquire(int arg) | 独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回;否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)方法 |
void acquireInterruptibly(int arg) throws InterruptedException | 与acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中;如果当前线程被中断,则该方法会招聘InterruptedException并返回 |
boolean tryAcquireNanos(int arg, long nanos) throws InterruptedException | 在acquireInterruptibly(int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回false,如果获取到了返回true |
void acquireShared(int arg) | 共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态 |
void acquireSharedInterruptibly(int arg) throws InterruptedException | 与acquireShared(int arg)方法相同,此方法响应中断 |
boolean tryAcquireSharedNanos(int arg, long nanos) throws InterruptedException | 在acquireSharedInterruptibly(int arg)方法基础上增加了超时限制 |
boolean release(int arg) | 独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒 |
boolean releaseShared(int arg) | 共享式的释放同步状态 |
Collection<Thread> getQueuedThreads() | 获取等待在同步队列上的线程集合 |
boolean hasQueuedPredecessors() | 判断是否有比当前线程等待时间更久的线程。可用于实现公平锁,即先排队的线程先得到锁。 |
可重写的方法
方法名称 | 说 明 |
---|---|
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() | 当前同步器是否在独占模式下被线程占用,一般此方法表示是否被当前线程所独占 |
protected final void setExclusiveOwnerThread(Thread thread) | 设置当前拥有独占访问权限的线程 |
访问|修改同步状态的方法
方法名称 | 说 明 |
---|---|
protected final int getState() | 获取当前同步状态 |
protected final void setState(int newState) | 设置当前同步状态 |
protected final boolean compareAndSetState(int expect, int update) | 使用CAS设置当前状态,此方法能够保证状态设置的原子性 |
// 不可重入锁
public class AQSLockTest implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
/**
* 尝试独占式获取
*/
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
// 设置当前拥有独占访问权限的线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 尝试独占式释放
*/
@Override
protected boolean tryRelease(int arg) {
if(0 == getState()){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
/**
* 此方法用于判断同步器是否被线程占用
*/
@Override
protected boolean isHeldExclusively() {
return 1 == getState();
}
Condition getCondition() {
return new ConditionObject();
}
Thread getOwnerThread() {
return getExclusiveOwnerThread();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
System.out.println(Thread.currentThread().getName()+" 准备获取锁");
sync.acquire(1);
System.out.println(Thread.currentThread().getName()+" 已经获取锁");
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
System.out.println(Thread.currentThread().getName()+" 准备释放锁");
sync.release(1);
System.out.println(Thread.currentThread().getName()+" 已经释放锁");
}
@Override
public Condition newCondition() {
return sync.getCondition();
}
Thread getOwnerThread() {
return sync.getOwnerThread();
}
private static CountDownLatch countDownLatch = new CountDownLatch(10);
private static class Test implements Runnable {
private AQSLockTest aqsLockTest;
public Test(AQSLockTest aqsLockTest) {
this.aqsLockTest = aqsLockTest;
}
@Override
public void run() {
try {
countDownLatch.await();
aqsLockTest.lock();
//Thread owner = aqsLockTest.getOwnerThread();
//System.out.println(owner.getName() + "拥有权限");
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "拥有权限");
SleepTools.ms(1);
} catch (Exception e) {
e.printStackTrace();
}finally {
aqsLockTest.unlock();
}
}
}
public static void main(String[] args) throws Exception {
AQSLockTest aqsLockTest = new AQSLockTest();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Test(aqsLockTest));
thread.setName("第" + i + "个线程");
thread.start();
countDownLatch.countDown();
}
}
}
以上代码实现了一个不可重入自定义独占锁,可以看到下图运行结果,它是一个队列锁,获取锁的顺序和排队顺序一致。
/**
* 可重入锁,主要改造tryAcquire(int arg)方法和tryRelease(int arg)方法;
* 测试每个线程拿两次锁,在run()中通过i处理拿两次限制测试
*/
public class AQSReentrantLockTest implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
/**
* 尝试独占式获取
*/
@Override
protected boolean tryAcquire(int arg) {
int tmp = getState();
Thread current = Thread.currentThread();
if (0 == tmp) {
if (compareAndSetState(0, 1)) {
// 设置当前拥有独占访问权限的线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}else if(current == getExclusiveOwnerThread()){
// 如果当前线程已经获取锁,则状态+1
int nextState = tmp + arg;
setState(nextState);
return true;
}
return false;
}
/**
* 尝试独占式释放
*/
@Override
protected boolean tryRelease(int arg) {
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
int tmp = getState() - arg;
boolean free = false;
if (0 == tmp) {
free = true;
setExclusiveOwnerThread(null);
}
setState(tmp);
return free;
}
/**
* 此方法用于判断同步器是否被线程占用
*/
@Override
protected boolean isHeldExclusively() {
return 1 == getState();
}
Condition getCondition() {
return new ConditionObject();
}
Thread getOwnerThread() {
return getExclusiveOwnerThread();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
System.out.println(Thread.currentThread().getName()+" 准备获取锁");
sync.acquire(1);
System.out.println(Thread.currentThread().getName()+" 已经获取锁");
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
System.out.println(Thread.currentThread().getName()+" 准备释放锁");
sync.release(1);
System.out.println(Thread.currentThread().getName()+" 已经释放锁");
}
@Override
public Condition newCondition() {
return sync.getCondition();
}
Thread getOwnerThread() {
return sync.getOwnerThread();
}
private static CountDownLatch countDownLatch = new CountDownLatch(10);
private static class Test implements Runnable {
private AQSReentrantLockTest aqsLockTest;
public Test(AQSReentrantLockTest aqsLockTest) {
this.aqsLockTest = aqsLockTest;
}
int i = 0;
@Override
public void run() {
try {
countDownLatch.await();
aqsLockTest.lock();
//Thread owner = aqsLockTest.getOwnerThread();
//System.out.println(owner.getName() + "拥有权限");
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "拥有权限");
SleepTools.ms(1);
i++;
if(i < 2){
// 每个线程拿两次锁,测试结果可以重入
this.run();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
aqsLockTest.unlock();
}
}
}
public static void main(String[] args) throws Exception {
AQSReentrantLockTest aqsLockTest = new AQSReentrantLockTest();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Test(aqsLockTest));
thread.setName("第" + i + "个线程");
thread.start();
countDownLatch.countDown();
}
}
}
解析AQS原理
节点Node
AQS有个内部类Node,Node上存储了比如节点的状态、前驱节点等。
当线程获取同步状态失败时,同步器会将当前线程及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程。当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态、前驱节点、后继节点。
Node平常需要关注的信息:
- 线程信息:当前Node是哪个线程
- 队列中线程的状态:当前线程的状态,如等待中、即将得到锁、已取消
- 前驱节点、后继节点:因为是一个等待队列,所以当前置线程释放锁后,当前线程得到通知去获取锁;当前线程释放锁后,通知后继线程去获取锁
Node类结构:
等待模式 | |
---|---|
SHARED | 线程以共享的模式等待锁(ReadLock) |
EXCLUSIVE | 线程以互斥的模式等待锁,一个锁只能有一个线程拥有(ReetrantLock) |
线程在队列中的状态枚举[初始化Node对象时,默认值为0] | |
CANCELLED | 值为1,表示线程已取消获取锁 |
SIGNAL | 值为-1,表示线程已就绪,只在等锁空闲时获取它 |
CONDITION | 值为-2,表示线程等待某个条件(Condition)被满足 |
PROPAGATE | 值为-3,当线程处在“SHARDE”模式时,此字段才会被使用 |
成员变量 | |
volatile int waitStatus | 表示线程在队列中的状态,值为CANCELLED、SIGNAL、CONDITION、PROPAGATE |
volatile Node prev | 前驱Node节点 |
volatile Node next | 后继Node节点 |
volatile Thread thread | 当前节点对应的线程 |
Node nextWaiter | 表示在等待condition条件的Node节点 |
首尾节点 | |
head | 首节点,指向队列头节点[head节点未保存线程信息,这种数据结构叫做“空头节点链表”] |
tail | 尾节点,指向队列尾节点 |
队列
节点加入到同步队列:
当线程获取同步状态失败时,AQS会将这个线程及等待状态等信息构造成Node节点并添加到同步队列尾部。为了保证加入队列的过程是安全的,同步器提供了一个基于CAS的设置尾节点的方法:private final boolean compareAndSetTail(Node expect, Node update),需要传递当前线程“认为”的尾节点的当前节点,只有设置成功后,当前节点才与之前的尾节点建立关联。
首节点的变化:
首节点即为获取同步状态成功的节点。首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头节点的方法并不需要使用CAS 来保证,它只需要将首节点设置成为原首节点的后继节点并断开原首节点的next 引用即可。
独占式获取同步状态:
此方法完成了同步状态获取、节点构造、加入同步队列、在同步队列中自旋等工作。
首先调用自定义同步器实现的tryAcquire(int arg)方法线程安全的获取同步状态。如果失败,则构造Node.EXCLUSIVE独占式同步节点,并通过addWaiter(Node node)方法将该节点加入到同步队列尾部。最后通过acquireQueued(Node node, int arg)方法使该节点以死循环的方式获取同步状态,如果获取不到,则阻塞节点中的线程,被阻塞线程的唤醒依靠前驱节点的出队或阻塞线程被中断来实现。
独占式释放同步状态:
总结:
获取状态失败的线程在队列中自旋acquireQueued()。当前驱节点为head节点并且获取了同步状态后,处理完业务逻辑会释放,释放时调用tryRelease(int arg)方法,唤醒head节点的后继节点,当后继节点无效时,从队列尾部开始遍历第一个有效的排队节点并唤醒。
公平锁和非公平锁:
_____个人笔记_____((≡^⚲͜^≡))_____欢迎指正_____