今天分享并发编程 AQS 思想之AbstractQueuedSynchronizer 类原理解析:
队列同步器
AbstractQueuedSynchronizer
(以下简称同步器或
AQS
),是用 来构建锁或者其他同步组件的基础框架,它使用了一个 int
成员变量表示同步状
态,通过内置的
FIFO
队列来完成资源获取线程的排队工作。并发包的大师(
Doug Lea)期望它能够成为实现大部分同步需求的基础。
一、AQS
使用方式和其中的设计模式
1、AQS
的主要使用方式是继承,子类通过继承
AQS
并实现它的抽象方法来管 理同步状态,在 AQS
里由一个
int
型的
state
来代表这个状态,在抽象方法的实 现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的 3
个方法 (getState()
、
setState(int newState)
和
compareAndSetState(int expect,int update)
)
来进行操作,因为它们能够保证状态的改变是安全的。
在实现上,子类推荐被定义为自定义同步组件的静态内部类,
AQS
自身没有 实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义 同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地 获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、 ReentrantReadWriteLock 和
CountDownLatch
等)。 同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步 器。可以这样理解二者之间的关系: 锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线 程并行访问),隐藏了实现细节; 同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、 线程的排队、等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者 所需关注的领域。 实现者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步 组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者 重写的方法。
2、模板方法模式 :
同步器的设计基于模板方法模式。模板方法模式的意图是,定义一个操作中 的算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。我们最常见的就是
Spring 框架里的各种 Template
。
实际例子
我们开了个蛋糕店,蛋糕店不能只卖一种蛋糕呀,于是我们决定先卖奶油蛋
糕,芝士蛋糕和慕斯蛋糕。三种蛋糕在制作方式上一样,都包括造型,烘焙和涂
抹蛋糕上的东西。所以可以定义一个抽象蛋糕模型
/**
* 类说明:抽象蛋糕模型
*/
public abstract class AbstractCake {
protected abstract void shape();
protected abstract void apply();
protected abstract void brake();
/*模板方法*/
public final void run(){
this.shape();
this.brake();
if(this.shouldApply()){
this.apply();
}
}
}
然后就可以批量生产三种蛋糕,其他两种代码类似:
/**
* 类说明:芝士蛋糕
*/
public class CheeseCake extends AbstractCake {
@Override
protected void shape() {
System.out.println("芝士蛋糕造型");
}
@Override
protected void apply() {
System.out.println("芝士蛋糕涂抹");
}
@Override
protected void brake() {
System.out.println("芝士蛋糕烘焙");
}
}
测试方法:
/**
* 类说明:生产蛋糕
*/
public class MakeCake {
public static void main(String[] args) {
AbstractCake cake = new CheeseCake();
// AbstractCake cake2 = new CreamCake();
//AbstractCake cake = new MouseCake();
cake.run();
AbstractCake smalCake = new SmallCake();
smalCake.run();
}
}
执行结果:
这样一来,不但可以批量生产三种蛋糕,而且如果日后有扩展,只需要继承 抽象蛋糕方法就可以了,十分方便,我们天天生意做得越来越赚钱。突然有一天, 我们发现市面有一种最简单的小蛋糕销量很好,这种蛋糕就是简单烘烤成型就可 以卖,并不需要涂抹什么食材,由于制作简单销售量大,这个品种也很赚钱,于 是我们也想要生产这种蛋糕。但是我们发现了一个问题,抽象蛋糕是定义了抽象的涂抹方法的,也就是说扩展的这种蛋糕是必须要实现涂抹方法,这就很鸡儿蛋疼了。怎么办?我们可以将原来的模板修改为带钩子的模板。
/**
* 类说明:抽象蛋糕模型
*/
public abstract class AbstractCake {
protected abstract void shape();
protected abstract void apply();
protected abstract void brake();
/*模板方法*/
// public final void run(){
// this.shape();
// this.apply();
// this.brake();
// }
/*模板方法*/
public final void run(){
this.shape();
this.brake();
if(this.shouldApply()){
this.apply();
}
}
protected boolean shouldApply(){
return true;
}
}
做小蛋糕的时候通过
flag
来控制是否涂抹,其余已有的蛋糕制作不需要任何 修改可以照常进行。
/**
* 类说明:小蛋糕
*/
public class SmallCake extends AbstractCake {
private boolean flag = false;
public void setFlag(boolean shouldApply){
flag = shouldApply;
}
//设置标识,判断使用
@Override
protected boolean shouldApply() {
return this.flag;
}
@Override
protected void shape() {
System.out.println("小蛋糕造型");
}
@Override
protected void apply() {
System.out.println("小蛋糕涂抹");
}
@Override
protected void brake() {
System.out.println("小蛋糕烘焙");
}
}
执行结果:发现只有小蛋糕只有两个方法执行
这就是模板方法的使用,AQS也是利用此中设计模式。
二、AQS
中的方法
1、模板方法
实现自定义同步组件时,将会调用同步器提供的模板方法,举例
new ReentrantLock();类 点击进入源码:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
2、点击NonfairSync 静态内部类:
/** 实现 Sync组件类
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
* 重写加锁方法
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());//独占式设置
else
acquire(1);
}
//重写尝试加锁方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
点击 Sync 进入静态内部类:
3、发现继承了
AbstractQueuedSynchronizer 抽象类,即Reentrantlock 里包含此内部类,而不是去继承此类,体现了使用者和开发者功能分开的思路。大致介绍AbstractQueuedSynchronizer的
主要方法:
这些模板方法同步器提供的模板方法基本上分为
3
类:独占式获取与释放同 步状态、共享式获取与释放、同步状态和查询同步队列中的等待线程情况。
4、可重写的方法
5、访问或修改同步状态的方法 :
重写同步器指定的方法时,需要使用同步器提供的如下
3
个方法来访问或修 改同步状态。
•
getState()
:获取当前同步状态。
•
setState(int newState)
:设置当前同步状态。
•
compareAndSetState(int expect,int update)
:使用
CAS
设置当前状态,该方 法能够保证状态设置的原子性。
AbstractQueuedSynchronizer 类原理解析分享完成,下篇我们手动实现一个自己的独占式显示锁,思路类似ReentrantLock类,敬请期待!