一 概念 AQS(AbstractQueuedSynchronizer) 抽象的队列同步器,它提供了一个FIFO队列(即普通先进先出队列),可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。
AQS是一个抽象类,主要是通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取以及释放的方法来提供自定义的同步组件。
名词说明: 本文中 锁 == 同步状态
二 AQS的用法
2.1 锁的状态:
AQS对象内部有一个核心的变量叫做state,是volotile int state,代表了加锁的状态
getState();//获取当前同步状态
setState();//设置当前同步状态,方法不能保证原子性
compartAndSetState();//设置当前同步状态,原子操作(CAS)
2.2 AQS内常见的方法
独占式锁
accquire();//获取锁,阻塞的
accquireInterruptibly();//获取锁,方法可以响应中断
tryAccquireNanos();// 尝试获取锁
release();//释放锁
共享式锁
accquireShared();//获取锁
accquireSharedInterruptibly();//获取锁,方法可以响应中断
tryAccquireSharedNanos();// 尝试获取锁
releaseShared();//释放锁
2.3 如何使用AQS实现同步器
AQS从字面意思就能知道是一个抽象类,所以需要继承它,并根据需要重写其中的模板方法
需要子类重新的方法,(对应下列代码中内部类中重写方法)
独占式
tryAccquire();//尝试获取锁
tryRelease();//尝试释放锁
共享式
tryAccquireShared();//尝试获取锁
tryReleaseShared();//尝试释放锁
通用
boolean isheldexclusively();//是否处于独占锁定状态
2.4 写一个自己的锁
/** * 实现自己的锁 * * @author hup * @since 2018-4-20 9:27 */ public class SelfLock implements Lock { /** * 同步器 内部类 */ private static class Sync extends AbstractQueuedSynchronizer { //state 表示锁标识, ==1 标识被获取,==0 未被获取 /** * 尝试获取锁 * * @param arg * @return */ @Override protected boolean tryAcquire(int arg) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } /** * 尝试释放锁 * * @param arg * @return */ @Override protected boolean tryRelease(int arg) { if (getState() == 0) { //抛出异常,当前锁无需释放的 throw new IllegalMonitorStateException(); } setExclusiveOwnerThread(null); //此次不用原子操作的原因是没必要,因为释放锁的线程肯定已经获取到锁,释放锁不存在竞争 setState(0); return true; } /** * 是否被独占线程占用了锁 * @return */ protected boolean isHeldExclusively() { return getState() == 1; } /** * 获取conditon 对象,用于等待和通知 * @return */ protected Condition newCondition() { return new ConditionObject(); } } //同步器对象 private final Sync sync = new Sync(); /** * 获取锁 */ @Override public void lock() { sync.acquire(1); } /** * 获取锁,可以响应中断 * @throws InterruptedException */ @Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } /** * 尝试获取锁 * @return */ @Override public boolean tryLock() { return sync.tryAcquire(1); } /** * 尝试获取锁,有超时时间 * @param time * @param unit * @return * @throws InterruptedException */ @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(time)); } /** * 释放锁 */ @Override public void unlock() { sync.release(1); } /** * 获取condition 对象,用于通知和等待 * @return */ @Override public Condition newCondition() { return sync.newCondition(); } }
使用自己的锁
//自己的锁 SelfLock selfLock = new SelfLock(); selfLock.lock(); try { //具体业务 }finally { selfLock.unlock(); }
当然上面锁不支持重入,如果想自己的锁可重入,那对state就要多几个值管理了,比如可以定为0--9 10个值,即最多可允许获取10次锁
三 AQS中同步器队列分析
3.1 同步队列
同步器依赖内部的一个同步队列来完成获取同步状态的管理。当线程获取同步状态失败时,会被加入到队列中,并同时阻塞线程。
当非同步队列中的线程同步状态释放时,会把队列中首节点中的线程唤醒,使其再尝试获取同步状态。
为什么是尝试: 因为此时不一定就100%能获取到锁,万一中途可能有新线程正好进入,然后获取了锁。
每个线程获取同步状态失败时,会被加入队列,此时该线程是被包装为一个Node节点加入
Node节点各属性
字段名 | 描述 |
int waitStatus |
1 线程等待超时或者中断了,需要从队列中移走 -1 当前节点完成工作后,可以通知其他后面节点去运行 -2 节点在等待队列中,当condition被signal()后,会从等待队列转到同步队列 -3 共享,表示状态要往后面的节点传播 0 初始状态 |
Node prev | 前驱节点 |
Node next | 后继节点 |
Node nextWaiter |
等待队列中的后继节点,如果当前节点是共享的,则这个字段将是一个SHARED常量, 也就是说节点类型(独占或共享)和等待队列中的后继节点共用同一字段 |
Thread thread | 获取同步状态的线程 |
注意上面说的 waitStatus 和 status 不是一个东西
waitStatus用于记录节点的状态,state用于描述锁状态的(标记是否处于同步中,以及记录重入次数)
同步队列的基本结构
往队列尾添加元素的时候,为了保证操作时线程安全的,同步器提供了 同步器提供了一个compareAndSetTail(Node expect, Node update) 原子操作方法。
同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。
当非队列内线程持有锁并释放锁后,会把队列中首节点中的线程唤醒,使其再尝试获取同步状态。
为什么是尝试: 因为此时不一定就100%能获取到锁,万一中途可能有新线程正好进入,然后获取了锁
独占式获取同步锁流程图
独占式的获取同步状态的流程图
3.2 等待队列
AQS中的等待队列是由内部类ConditionObject维护是Condition的实现。子类中实现了await开头的方法,signal开头的方法,其中以await开头都是将线程设置成等待状态,而signal使用来唤醒被被等待的线程。
当前线程调用了await()方法后,将当前线程封装为Node节点并添加到等待队列尾部
当前线程调用了signal()方法后,完成的工作是,添加等待节点的头节点到同步对列中,唤醒线程
完结