多线程的学习(七) AQS的简单学习
在之前的CountDownLatch
的学习时候,接触到了一个AbstractQueuedSynchronizer
类,简单的学习一下这个类。
什么是AQS
- AQS,全称
AbstractQueuedSynchronizer
,名字抽象队列同步器 - JAVA并发包中,有许多API都是基于AQS来实现的加锁与释放锁的,比如常见的
ReentrantLock
,ReentrantReadWriteLock
,又比如CountDonLatch
等等
一个加锁的流程
现在假如我们使用
ReentrantLock
中lock()
进行加锁//构造方法 public ReentrantLock() { sync = new NonfairSync(); } //内部类 static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } //方法 public void lock() { sync.acquire(1); }
通过源代码的查看,发现,他其实是调用了一个内部类来执行方法,内部类继承了
Sync
类,而这个Sync
类,继承了AbstractQueuedSynchronizer
类-
简单分析一下AQS对象,
-
他有一个核心的变量state,用来记录 加锁的状态,默认为0
private volatile int state;
-
他还有个一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。
private transient Thread exclusiveOwnerThread;
-
结构图:
- image-20200711072741892.png
当线程调用
lock()
进行加锁的时候,实际上就是通过CAS将state
变为1-
然后加锁成功,设置当前的加锁对象,所以具体流程就是这样的:
- image-20200711073156437.png
-
-
知道了一个流程,那么可重入锁怎么实现的?
- 每次线程1可重入加锁一次,会判断一下当前加锁线程就是自己,那么他自己就可以可重入多次加锁,每次加锁就是把state的值给累加1,别的没啥变化。
-
当线程2过来的时候,发现state不是0,就通过
acquireQueued()
放入队列进行等待,等待线程1释放锁
image.png
- 然后线程1释放锁,就是将
AQS
内的state
变量的值递减1,如果state
值为0,则彻底释放锁,会将“加锁线程”变量也设置为null
- 等待队列的队头唤醒线程2重新尝试加锁。
- 循环上述操作
总结
- 上面简单的介绍了AQS的独占模式。
- AQS中还提供了另外一种模式,共享模式
- 一开始给state设定一个值,当一个线程获取资源后,state就减去1,其它线程再获取资源来时,state再次减去1.
- 以此类推,直到线程获取资源减到为0为止。表示资源没有了其他线程就无法获取了只能去等待了。