并发编程学习总结(一、AQS总结)
目录:
- AQS整体介绍
- AQS类图
- AQS实现
AQS总结:
AQS及其实现类已经学完了,今天我就来按照自己的理解简单的总结下AQS。
首先AQS全称为AbstractQueuedSynchronize,从含义上可以看出它是队列同步器,是用于构建锁或其它同步组件的基础框架。
AQS整体介绍:
AQS这个基础框架提供了共享锁、排它锁的实现,并且这两种锁都具有获取锁和释放锁两种功能;以及AQS中比较重要的ConditionObject、Node。
1、共享锁:
- 获取锁:java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireShared
- 释放锁:java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
2、排它锁:
- 获取锁:java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
- 释放锁:java.util.concurrent.locks.AbstractQueuedSynchronizer#release
3、ConditionObject(java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject):条件锁的实现类,类似于Object中wait、notify。
4、Node:实现AQS的基础数据结构,基于链表实现。
AQS类图:
1、AQS直接实现类:
AQS默认的直接实现类有三个,分别是ReentrantLock、Semaphore、CountDownLatch,这三个实现类都是通过自己的内部类Sync来实现AQS的。
其中ReentrantLock、Semaphore实现了公平锁和非公平锁。
2、间接实现类:
从图中可以看出AQS间接实现类都是基于ReentrantLock和Condition实现的,其中Condition的默认实现就是AQS中的ConditionObject。
AQS实现:
AQS基于双向链表实现,其获取锁、释放锁采用模板模式,具体的获取释放逻辑交给子类实现,极大的增加了框架的灵活度。
1、双向链表:
1 public abstract class AbstractQueuedSynchronizer 2 extends AbstractOwnableSynchronizer 3 implements java.io.Serializable { 4 5 /** 6 * 队列节点数据类 7 */ 8 static final class Node { 9 /** 前驱节点 */ 10 volatile Node prev; 11 /** 后继节点 */ 12 volatile Node next; 13 /** 下一个等待节点,condition模式下使用 */ 14 Node nextWaiter; 15 } 16 17 /** 头结点 */ 18 private transient volatile Node head; 19 /** 尾结点 */ 20 private transient volatile Node tail; 21 22 }
2、模板模式:
1 public final void acquire(int arg) { 2 if (!tryAcquire(arg) && 3 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 4 selfInterrupt(); 5 } 6 7 protected boolean tryAcquire(int arg) { 8 throw new UnsupportedOperationException(); 9 } 10 11 public final boolean release(int arg) { 12 if (tryRelease(arg)) { 13 Node h = head; 14 if (h != null && h.waitStatus != 0) 15 unparkSuccessor(h); 16 return true; 17 } 18 return false; 19 } 20 21 protected boolean tryRelease(int arg) { 22 throw new UnsupportedOperationException(); 23 }
可以看出排它锁的获取锁、释放锁的实现都抛出了异常,它会交给子类实现;共享锁也是如此,这里不再赘述。
因为之前章节已经详述过具体实现逻辑了,这里就不再细说,我直接总结下流程:
1、排它锁:
- 获取锁:java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
- 获取成功:直接结束acquire方法,去执行线程内部逻辑。
- 获取失败:
- 通过addWaiter方法入队,以CAS的方式进入队尾。
- CAS成功,直接入队尾。
- CAS失败,通过自旋的方式一直CAS直到入队成功。
- 入队后addWaiter返回入队节点Node,并执行acquireQueued函数。
- 若传入acquireQueued函数的Node的前驱节点为头节点就会尝试获取锁,获取成功后返回中断标识。
- 若非上述情况,Node的前驱节点不为头节点或获取锁失败了,就会调用shouldParkAfterFailedAcquire与parkAndCheckInterrupt来维护中断标识。
- shouldParkAfterFailedAcquire:检查并更新已经取消了的线程,如果线程需要被阻塞则返回true,否则返回false。
- 通过addWaiter方法入队,以CAS的方式进入队尾。
- 释放锁:java.util.concurrent.locks.AbstractQueuedSynchronizer#release
- 释放成功:
- 头节点状态为0,方法直接返回true。
- 头节点状态不为0,调用unparkSuccessor方法,改方法用于唤醒头节点的后继节点,并且还会清理已取消的线程。
- 从函数可以看出释放锁,只会释放头节点的,因为队列也是先进先出的嘛。
- 释放成功:
-
- 释放失败:方法返回false。
2、共享锁:可参考https://www.cnblogs.com/bzfsdr/p/13141361.html中的共享锁和独占锁在源码上有何区别。
3、ConditionObject:最核心的逻辑就是等待用LockSupport.park(),唤醒用LockSupport.unpark();然后就是条件队列用的是nextWaiter字段,同步队列是用prev、next记住这点就好了,其它的大同小异,当然有些特殊的处理逻辑还需要你自己取琢磨。
- 线程等待:java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#await()。
- 若线程已经中断,则抛出InterruptedException(响应中断标识)。
- 将当前线程加入到条件队列中(设置Node节点的nextWaiter属性)。
- 释放锁,调用release()方法成功后则释放,否则抛出非法的锁状态异常(因为进await()方法时并没有判断当前线程是否持有这把锁)。
- 当当前线程为条件队列时,调用LockSupport.park()锁住ConditionObject对象,会在调用signal时是否锁,并往后执行。
- 之后就是一些异常流程的处理。
- 线程唤醒:java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#signal()。
- 判断当前线程是否持有锁,若未持有则抛出IllegalMonitorStateException。
- 将条件队列上所有节点清空(断开nextWaiter),并自旋的加入条件队列后执行LockSupport.unpark()解锁。