本文正在参加「Java主题月 - Java开发实战」,详情查看:juejin.cn/post/696719…
这是我参与更文挑战的第3天,活动详情查看: 更文挑战
AbstractQueuedSynchronizer
cookBook - ifeve.com/doug-lea/
@todo 564
子类
- ReentrantLock 中的 async
- CountdownLatch 中的 async
- LimitLatch 中的 async
- Semaphore 中的 async
- ReentrantReadWriteLock 中的 async
- ThreadPoolExecutor 中的 worker
实现原理
先回顾一下,在不显式依赖任何Lock包相关实现的情况下,我们如何做到线程安全?
-
Synchronized
- 用一个底层实现的monitor对象,作为竞争对象的锁实例。
- 将monitor对象的地址(指针)信息记录在synchronized的对象头中
- 一旦需要获取这把锁,就做竞争。轻量级锁就是CAS变更+自旋,重量级锁就涉及到内核态到用户态的切换
-
volatile实现可见性与顺序性
-
Atmoic包实现无锁编程
synchronized实现了线程安全性上的”全能“,通过实现原理,做出一个猜想:
通过外加状态值,对状态值进行竞争,是否可以做到在Java代码层面实现在JVM中Synchronized的效果?AQS是一个值得参考的答案。
注释
- 提供了依赖于FIFO等待队列的,阻塞锁以及相关同步器(信号/事件)的实现。这个类,是大部分依赖于单一原子整型值来标识状态的同步器的基础。
- 本类支持默认的互斥模式和共享模式。尝试获取的线程需要判断是否获取成功。不同模式下的等待线程,共享同一个FIFO队列。 实现类通常只实现一个模式,但也有两个模式都支持的。
代码
类属性
配置值
- 自旋阈值
static final long spinForTimeoutThreshold = 1000L;
结构相关
- 头节点
- 懒加载,除初始化外仅在setHead中被修改。
- 如果head存在,它的等待状态必须确保不能为cancelled。
private transient volatile Node head;
-
尾指针
- 懒加载
- 仅在入列方法添加新的等待节点时会被修改。
private transient volatile Node tail; -
状态值
private volatile int state;
类方法
通用方法
内部提供了一些基于CAS的方法,用于原子性质的修改。
- 都是对unsafe类提供的native实现进行的封装。
清单
protected final boolean compareAndSetState(int expect, int update)
内部类
node
根据实现的注释,AQS内部是维护了一个FIFO队列的,这个就是FIFO队列的节点实现类,用来存储队列中线程相关信息的。
- 等待队列是CLH锁队列的一个实现,CLH锁常用在资源锁的实现上,这里的是用来实现阻塞同步器了。
需要注意的一点是,node是一个标注为final的类,因此在继承AQS的实现类中,等待队列的内部实现都是相同的。
-
属性清单
static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter;
类属性
节点属性
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
节点状态值
volatile int waitStatus;
这个状态值有几种:
- signal(-1)
- 当前节点的前节点正在(或马上要)阻塞(通过park),因此当前节点必须unpark前节点,当前节点释放或取消。
- 为避免竞争,获取的方法必须先判断是否需要信号,然后重试获取,失败则阻塞。
- cancelled(1)
- 当前节点因为超时或被中断而取消。
- 节点并不会保持在这个状态。
- 该状态的线程永远不会再阻塞了。
- condition(-2)
- 当前节点此时正在条件队列中(Condition queue)。
- 不会被用作同步队列节点,直到当状态再转化后变成0为止。
- 0并不代表任何此属性的其他用途,只是单纯简化机制。
- propagate(-3)
- 一个
releaseShared应当被传播到其他节点。 - 这个值只会被设定在头节点上。
- 在
doReleaseShared中被设置,用于确保传播行为持续进行,即使因为中断,其他线程已经做完这个工作了。
- 一个
- 0
- 非以上的所有情况。
使用时的额外说明:
-
非负值代表节点不需要
signal。 -
初始值为0,条件节点则是-2(Condition)。
-
通过CAS进行修改(当条件允许时亦会用非条件的
volatile写。)
相关节点
-
位置相关
volatile Node prev; volatile Node next;-
前节点
-
在进入队列时赋值,在出列时变为null(因为GC)。
-
当前驱节点取消了,@todo
we short-circuit while finding a non-cancelled one会一直存在因为头节点时不会被取消的:一个节点仅当获取成功的时候变成头节点。
-
取消状态的线程在acquire时是不可能成功的,而且线程只能取消它自身,不能取消其他线程的。
-
-
后节点
-
代表正在等待释放的待唤醒节点。在入列中初始化,在
bypassing cancelled predecessors(@todo)时调整,在出列时为null。 -
入列操作不会赋值,而是下一个attach操作执行时方才赋值。因此下一个节点为null不必然代表节点在队列末尾,我们会从tail开始双向扫描来确保末尾与否。
-
取消状态的节点的该引用会指向节点自身而不是空。
-
-
-
功能相关
volatile Thread thread; Node nextWaiter;- 下个等待节点
- 等待Condition,或特殊值SHARED态的节点。
- 因为条件队列仅当互斥模式时被访问,因此此处只用了一个简单的链表来维护等待条件的节点。
- 这些节点会被转化到重获取的队列中。
- 因为条件只能是互斥的,因此我们用了一个特殊值来表明share模式。(部分解释了上面的shared的值)
- 下个等待节点