深⼊理解AQS和CAS原理课程
AQS 全称是 Abstract Queued Synchronizer,⼀般翻译为同步器。它是⼀套实现多线程同步功能的框架。AQS 在源码中被⼴泛使⽤,尤其是
synchronized 功能差不多,但是拓展性
在 JUC(Java Util Concurrent)中,⽐如 ReentrantLock(跟Java的 synchronized
助你一臂之力synchronized 好)、Semaphore(信号量,更好控制并发数)、CountDownLatch(更好实现线程间交互。例如可以实现九宫格图⽚集⽐ synchronized
体加载完,再进⾏下⼀步。已经加载完的进⼊阻塞,等待其他线程加载完)、ThreadPoolExecutor(线程池)。
ReentrantLock 和 AQS 的关系
本课主要通过 ReentrantLock 来理解 AQS 内部的⼯作机制。⾸先从 ReentrantLock 的 lock() ⽅法开始:dirty
lock⽅法
boat怎么读代码很简单,只是调⽤了⼀个 Sync 的 lock() ⽅法。
可以看出,Sync 是 ReentrantLock 中的⼀个内部类。ReentrantLock 并没有直接继承 AQS,⽽是通过内部类 Sync 来扩展 AQS 的功能,然后 ReentrantLock 中存有 Sync 的全局变量引⽤。
潜水是什么意思Sync 在 ReentrantLock 有两种实现:NonfairSync 和 FairSync,分别对应⾮公平锁和公平锁。以⾮公平锁为例,实现源码如下:
NonfairSync ⾮公平锁
aiku可以看出在⾮公平锁中的 lock() ⽅法中,主要做了如下操作:
1.如果通过 CAS 设置变量 State(同步状态)成功,表⽰当前线程获取锁成功,则将当前线程设置为独占线程。
2.如果通过 CAS 设置变量 State(同步状态)失败,表⽰当前锁正在被其他线程持有,则进⼊ Acquire ⽅法进⾏后续处理。acruire() ⽅法定义在 AQS 中,具体如下:
acquire() ⽅法是⼀个⽐较重要的⽅法,可以将其拆解为 3 个主要步骤:
2.addWaiter() 如果 tryAcquire() 尝试获取锁失败则调⽤ addWaiter 将当前线程添加到⼀个等待队列中;
3.acquireQueued 处理加⼊到队列中的节点,通过⾃旋去尝试获取锁,根据情况将线程挂起或者取消。
以上 3 个⽅法都被定义在 AQS 中,但其中 tryAcquire() 有点特殊,其实现如下:
tryAcquire()
默认情况下直接抛异常,因此它需要在⼦类中复写,也就是说真正的获取锁的逻辑由⼦类同步器⾃⼰实现。
ReentrantLock 中 tryAcquire 的实现(⾮公平锁)如下:
解释说明:
1.获取当前线程,判断当前的锁的状态;
2.如果 state=0 表⽰当前是⽆锁状态,通过 cas 更新 state 状态的值,返回 true;
3. 如果当前线程属于重⼊,则增加重⼊次数,返回 true;
火花塞型号
4.上述情况都不满⾜,则获取锁失败返回 fal。
最后⽤⼀张图表⽰ ReentrantLock.lock() 过程:
软件培训机构
ReentrantLock.lock() 过程
从图中我们可以看出,在 ReentrantLock 执⾏ lock() 的过程中,⼤部分同步机制的核⼼逻辑都已经在 AQS 中实现,ReentrantLock ⾃⾝只要实现某些特定步骤下的⽅法即可,这种设计模式叫作模板模式。Activity 的⽣命周期⽅法就是使⽤模版模式, Activity 的⽣命周期的执⾏流程都已经在 framework 中定义好了,⼦类 Activity 只要在相应的 onCreate、onPau 等⽣命周期⽅法中提供相应的实现即可。
口信注意:不只 ReentrantLock,JUC 包中其他组件例如 CountDownLatch、Semaphor 等都是通过⼀个内部类 Sync 来
继承 AQS,然后在内部中通过操作 Sync 来实现同步。这种做法的好处是将线程控制的逻辑控制在 Sync 内部,⽽对外⾯向⽤户提供的接⼝是⾃定义锁,这种聚合关系能够很好的解耦两者所关注的逻辑。
AQS 核⼼功能原理分析
⾸先看下 AQS 中⼏个关键的属性,如下所⽰:
代码中展⽰了 AQS 中两个⽐较重要的属性 Node 和 state。
sta te 锁状态
state 表⽰当前锁状态。当 state = 0 时表⽰⽆锁状态;当 state>0 时,表⽰已经有线程获得了锁,也就是 state=1,如果同⼀个线程多次获得同步锁的时候,state 会递增,⽐如重⼊ 5 次,那么 state=5。 ⽽在释放锁的时候,同样需要释放 5 次直到 state=0,其他线程才有资格获得锁。
state 还有⼀个功能是实现锁的独占模式或者共享模式。
独占模式:只有⼀个线程能够持有同步锁。
⽐如在独占模式下,我们可以把 state 的初始值设置成 0,当某个线程申请锁对象时,需要判断 state 的值是不是 0,如果不是 0 的话意味着其他线程已经持有该锁,则本线程需要阻塞等待。
共享模式:可以有多个线程持有同步锁。
在共享模式下的道理也差不多,⽐如说某项操作我们允许 10 个线程同时进⾏,超过这个数量的线程就需要阻塞等待。那么只需要在线程申请对象时判断 state 的值是否⼩于 10。如果⼩于 10,就将 state 加 1 后继续同步语句的执⾏;如果等于 10,说明已经有 10 个线程在同时执⾏该操作,本线程需要阻塞等待。
N o de 双端队列节点
Node 是⼀个先进先出的双端队列,并且是等待队列,当多线程争⽤资源被阻塞时会进⼊此队列。这个队列是 AQS 实现多线程同步的核⼼。
从之前 ReentrantLock 图中可以看到,在 AQS 中有两个 Node 的指针,分别指向队列的 head 和 tail。
Node 的主要结构如下:
默认情况下,AQS 中的链表结构如下图所⽰:
获取锁失败后续流程分析
锁的意义就是使竞争到锁对象的线程执⾏同步代码,多个线程竞争锁时,竞争失败的线程需要被阻塞等待后续唤醒。那么 ReentrantLock 是如何实现让线程等待并唤醒的呢?
前⾯中我们提到在 ReentrantLock.lock() 阶段,在 acquire() ⽅法中会先后调⽤ tryAcquire、addWaiter、acquireQueued 这 3 个⽅法来处理。tryAcquire 在 ReentrantLock 中被复写并实现,如果返回 true 说明成功获取锁,就继续执⾏同步代码语句。可是如果 tryAcquire 返回fal,也就是当前
锁对象被其他线程所持有,那么当前线程会被 AQS 如何处理呢?
addWaiter
invite⾸先当前获取锁失败的线程会被添加到⼀个等待队列的末端,具体源码如下:
>aimee allen