1、概述
AQS,即AbstractQueuedSynchronizer,抽象的队列式同步器。AQS定义了一套多线程访问共享资源的同步器框架,许多我们使用的同步器都是基于它来实现的,如常用的ReentrantLock、Semaphore、CountDownLatch、CyclicBarrie并发类都是通过实现AQS里面的模板方法来实现内部的组件。
基于AQS构建同步器:
- ReentrantLock
- Semaphore
- CountDownLatch
- ReentrantReadWriteLock
- SynchronusQueue
- FutureTask
2、基本框架
基本模型
获取同步状态:线程1获取状态由0修改为1,线程2获取状态为1就创建Node2结点放入队尾并且阻塞线程,后面依次内推
释放同步状态:线程1执行完成后,将state由1修改为0,并唤醒队列之后的Node2结点,并且删除Node1结点
AQS实现原理依赖内部State(同步状态)和CHL队列(FIFO双向队列),如果当前线程获取State同步状态失败,AQS就会将该线程以及状态等信息构造一个Node结点,并将这个Node结点添加到队尾,同时阻塞当前线程,当同步状态释放的时候,唤醒队列头结点
核心成员变量和方法
AQS的三个核心成员变量,都是用volatile关键字修饰的,保证了多线程下的可见性
/**
* 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;
head:是CHL队列的头结点,延迟初始化。除了初始化,只可以通过setHead() 方法进行修改
tail:是CHL队列的尾结点,延迟初始化,,只可以通过enq() 方法新增等待的节点
State:同步状态可以通过
getState():获取同步状态。setState(int newState):设置同步状态。compareAndSetState(int expect, int update):通过CAS方式修改同步状态。
资源共享方式
独占式(Exclusive):只有单个线程能够成功呢获取资源并执行,如ReentrantLock。共享式(Shared):多个线程可成功获取资源并执行,如Semaphore、CountDownLatch等。
AQS将大部分的同步逻辑均已经实现好了,继承的自定义同步器只需要实现state的获取(acquire)和释放(release)的逻辑代码就可以了。 独占
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo111 {
Lock lock=new ReentrantLock();
AbstractQueuedSynchronizer aqs=new AbstractQueuedSynchronizer() {
@Override
protected boolean tryAcquire(int arg) {
//独占方式。尝试获取资源,成功则返回true,失败则返回false。
return super.tryAcquire(arg);
}
@Override
protected boolean tryRelease(int arg) {
//独占方式。尝试释放资源,成功则返回true,失败则返回false。
return super.tryRelease(arg);
}
@Override
protected int tryAcquireShared(int arg) {
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
return super.tryAcquireShared(arg);
}
@Override
protected boolean tryReleaseShared(int arg) {
//共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
return super.tryReleaseShared(arg);
}
@Override
protected boolean isHeldExclusively() {
//该线程是否正在独占资源。只有用到condition才需要去实现它。
return super.isHeldExclusively();
}
@Override
public String toString() {
return super.toString();
}
};
}
一般自定义同步器不是独占就是共享方式,只需要实现tryAcquire-tryRelease或者 tryAcquireShared-tryReleaseShared 中的一组就可以了,AQS也支持子类同时实现独占和共享两种模式,如ReentrantReadWriteLock
基本原理
AQS是一个抽象类,可以用来构造锁和同步类,如ReentrantLock,Semaphore,CountDownLatch,CyclicBarrier。
AQS的原理是,AQS内部有三个核心组件,一个是state代表加锁状态初始值为0,一个是获取到锁的线程,还有一个阻塞队列。 当有线程想获取锁时,会以CAS的形式将state变为1,CAS成功后便将加锁线程设为自己。当其他线程来竞争锁时会判断state是不是0,不是0再判断加锁线程是不是自己,不是的话就把自己放入阻塞队列。这个阻塞队列是用双向链表实现的
可重入锁的原理就是每次加锁时判断一下加锁线程是不是自己,是的话state+1,释放锁的时候就将state-1。当state减到0的时候就去唤醒阻塞队列的第一个线程。
为什么AQS使用的双向链表?
因为有一些线程可能发生中断,而发生中断的时候就需要再同步阻塞队列中删除掉,这个时候用双向链表就方便删除掉中间的结点# AQS 详解