文章目录
- 引言
- 基础知识
- 简介
- AQS接口和示例
- 第一类:访问和修改同步状态的方法
- 第二类,5个重写的方法
- 第三类,9个模板方法
- 队列同步器实现原理
- 同步队列
- 独占式同步获取和释放
- 共享式同步获取和释放
- 独占式同步获取和释放
- 相关面试题
- 怎么理解Lock和AQS的关系
- 什么是AQS
- AQS是怎么实现同步管理的?底层数据结构?
- AQS有哪些核心方法
- 总结
引言
- 学了锁,学了多线程编程,学了线程池,发现很多都提到了AQS,现在来补一下,也是填一下以前留下的坑。
- 坑位
- 还是跟以前一样,先过一遍基础知识,然后再过一遍相关的面试题
基础知识
简介
- 队列同步器AbstractQueuedSynchronizer
-
构建锁和其他同步组建的基础框架
- 使用int变量表示同步状态,通过内置的队列完成线程排队工作,是实现大部分同步需求的基础
- 仅仅定义若干同步状态的获取和释放方法,来实现线程同步,没有实现任何接口
- 支持独占式和共享式获取同步状态
-
使用方式:继承
- 子类通过继承并实现方法管理同步状态,使用getState(),setState()和compareAndSetState来修改状态
-
AQS接口和示例
-
AQS的实现采用的是模板模式,使用他对外提供的三类方法来实现一个同步组件。
- 这里又不知道模板模式了,得去好好学一下了,学习链接
-
三类方法分别如下
- 第一类:同步器提供的用于访问和修改同步状态的三个方法
- 第二类:继承AQS并重写的5个方法
- 第三类:9个模板方法,用来组合AQS形成自定义的同步组件
这里给一个自定义的锁的样例mutex
-
第一步先在同步器中创建一个内部类继承并实现AQS,重写对应5个方法
-
调用9个模板方法,组合对应同步器的逻辑
第一类:访问和修改同步状态的方法
重写同步器指定的5个方法时,需要使用同步器提供的如下的三个方法来访问和修改同步状态
- getState:获取当前同步状态
- setState(int newState):设置当前同步状态
- CompareAndSetState(int expect,int update):使用CAS设置当前状态,保证状态设置的原子性
第二类,5个重写的方法
- isHeldExclusively:
- 判定该线程是否正在独享资源,只有用到condition的时候,才需要实现它
- tryAcquire(int)
- 独占的方式,尝试获取资源,成功就返回true,失败就返回false
- tryRelease(int)
- 独占的方式,尝试释放资源,成功就返回true,失败就返回false
- tryAcquireShared
- 共享方式,尝试获取资源
- 负数表示失败,0表示成功但是没有可用资源剩余,正数表示成功并且有剩余资源
- tryReleaseShared
- 共享方式,尝试释放资源
- 成功返回true,失败返回false
第三类,9个模板方法
模板方法主要分成三类
- 独占式获取和释放同步状态
- 共享式获取和释放同步状态
- 查询同步队列中的等待线程
第一类,独占式获取和释放同步状态
-
void acquire
- 独占式获取同步状态,获取成功由该方法返回,否则进入同步队列等待
- 调用重写的tryAcquire方法
-
void acquireInterruptibly
- 同上,但是支持响应中断
- 当前线程未获取到同步状态,而进入同步队列中,如果线程被中断,该方法抛出InterruptedException
-
void tryAcquireNanos
- 同上,但是支持响应中断,而且增加了超时机制
- 当前线程在超时时间内没有获取到同步状态,就会返回false,否则返回true
-
bool release
- 独占式释放同步状态
- 释放同步状态后,将同步队列中的第一个节点唤醒
第二类,共享式获取和释放同步状态
-
void acquireShared
- 共享式获取同步状态,获取成功由该方法返回,否则进入同步队列等待
- 与独占式的区别是同一时刻,有多个线程获取同步状态
-
void acquireSharedInterruptibly
- 同上,但是支持响应中断
- 当前线程未获取到同步状态,而进入同步队列中,如果线程被中断,该方法抛出InterruptedException
-
void tryAcquireSharedNanos
- 同上,但是支持响应中断,而且增加了超时机制
- 当前线程在超时时间内没有获取到同步状态,就会返回false,否则返回true
-
bool releaseShared
- 共享式释放同步状态
- 释放同步状态后,将同步队列中的第一个节点唤醒
第三类、获取同步队列中的线程
- collection getQueueThread
- 获取等待在同步队列上的线程的集合
这里给一个自定义的锁的样例mutex
- 第一步先在同步器中创建一个内部类继承并实现AQS,重写对应5个方法
- 调用9个模板方法,组合对应同步器的逻辑
具体代码
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class Mutex implements Lock {
// 定义一个静态内部类,用来继承AQS
private static class Sync extends AbstractQueuedSynchronizer{
// 是否处于占用状态
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 当状态为0的时候获取锁
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 释放锁,将状态设置为0
@Override
protected boolean tryRelease(int arg) {
if(getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 返回一个Condition,每个condition都包含了一个condition队列
Condition newCondition(){
return new ConditionObject();
}
}
// 将操作代理到Sync上
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 sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
总结
基本步骤如下
- 继承Lock接口
- 定义一个内部静态类AQS的子类,重写5个重写方法,调用3个状态修改函数实现
- 使用9个模板方法实现Lock规定的接口方法
- 将操作代理到AQS子类Sync上,利用Sync的14种方法来实现Lock
队列同步器实现原理
同步队列
队列节点
- 节点Node是构成同步队列的基础,获取同步状态失败的线程将会被包装成一个节点,加入到同步队列的尾部。
- 属性
- waitStatus等待状态
- cancelled表示线程超时或者中断,从队列中取消等待
- signal表示后继节点处于等待状态,当前节点释放同步状态会通知后继节点
- condition表示节点在等待队列中, 节点等待在Condition对象为锁的等待队列上,当其他线程对Condition调用signal方法时,该节点将等待队列转移到同步队列,加入到同步状态的获取中。
- propagate表示下一个共享式同步状态将会无条件传播下去
- prev和next:表示同步队列中的前驱节点和后继节点
- nextWaiter:等待队列中的后继节点
- waitStatus等待状态
独占式同步获取和释放
- 获取同步状态时,主要是通过维护一个同步队列实现
- 获取状态失败的线程会加入到同步队列中,自旋并尝试获取同步状态
- 释放同步状态,同步器调用tryRelease方法,释放同步状态,唤醒后继节点
共享式同步获取和释放
-
共享式同步获取实现
- 调用tryAcquireShared方法,根据返回的int值判定,大于等于零表示获取成功,这个值表示剩余的资源数。
-
调用releaseShared方法释放同步状态
独占式同步获取和释放
- 使用理解
- 强于Synchronized关键字,能够实现在指定时间内获取同步状态,调用同步器地doAcquireNanos实现
- 支持中断
- 设置时间过短
- 如果设置时间过短,会进入快速自旋过程
相关面试题
怎么理解Lock和AQS的关系
- Lock
- 面向锁的使用者,定义了使用者和锁的交互接口,隐藏了实现细节
- AQS
- 面向锁的开发者,简化了锁的实现,屏蔽了同步状态的管理,线程的排队等操作
- 二者很好地隔离了使用者和实现者所需要关注的领域
什么是AQS
- 队列同步器AQS(AbstractQueuedSynchronizer)
- 用来构建锁和实现其他同步组件的基础框架
- 使用int成员变量state表示同步状态
- 通过内置的FIFO队列完成想要获取资源的线程的排队工作
- 使用方式
- 通过继承,子类继承同步器并实现抽象方法来管理同步状态。
- 子类被推荐为自定义同步组建的静态内部类,同步器自身没有实现任何同步接口,仅仅是定义了若干同步状态获取和释放的方法来自定义同步组件
- 提供3个获取和修改同步状态的安全方法,getstate,setstate,compareAndSetState
- 同步器同时支持独占式获取同步状态和共享式获取同步状态
- 典型的应用
- ReentrantLock、读写锁ReenReadWriteLock等
- 通过继承,子类继承同步器并实现抽象方法来管理同步状态。
AQS是怎么实现同步管理的?底层数据结构?
- 实现方式
- 使用双向链表和使用volatile修饰的int型变量state
- state
- 0 没有线程占用同步资源
- 大于0 有线程占用同步资源
- 大于1 同步资源已经被占用了很多次
- 双向链表,基于FIFO的同步队列
- 通过将等待线程加入同步队列,实现线程获取同步状态失败的管理
- 释放同步状态的时候,从同步队列中唤醒等待线程,实现同步机制。
- state
- 使用双向链表和使用volatile修饰的int型变量state
- 典型应用
- 独占式:只有一个线程可以占用同步资源
- reentrantLock
- 共享式:多个线程占用共享资源
- CountDownLatch
- 独占式:只有一个线程可以占用同步资源
- 上述两种方式在底层都是AQS实现的,且实现方式基本相同,区别在于获取和释放同步状态的方式不同。
AQS有哪些核心方法
- 一共有三类方法
- 第一类:3个访问和修改同步状态的方法
- 第二类:5个需要重写的方法,调用上述三个方法重写
- 第三类:9个模板方法,由外界具体调用实现不同同步组件
- 三类方法关系:
- 实现一个同步组件时,使用者继承AbstractQueuedSynchronizer并重写5个指定的方法(第二类)。重写同步器指定的方法时,需要使用同步器提供的3个方法来访问或修改同步状态(第一类)。
- 最后将AQS组合在自定义同步组件的实现中,并调用其9个模板方法(第三类)和 5个重写过的方法来实现,另外模板方法会调用使用者重写的方法。
总结
- 我觉这些框架的东西不是很好理解,过分偏向于底层了,然后理解起来有比较费劲,背起来也比较难,问的也比较少,但是总是在很多地方有体现,这里还是得学习一下!
- 这个过了,多线程编程算是过了,还有一些并发容器(ConcurrentHashMap和CopyOnWriteList)以及并发工具类没有实现,这里还是抽空再看吧!感觉问到的比较少!
- 招工作真的不容易呀,学的我想死,每天的学习任务都好重!还是害怕找不到好工作!白读了研究生!