AQS源码分析

AQS(队列同步器)源码分析

AQS源码详细解读 - 知乎 (zhihu.com)

JavaGuide

实现类:ReentrantLockCountDownLatch、CyclicBarrier、Semaphore

  • AQS重要成员变量(state)
  • 内部类-Node(等待队列的实现)
  • 获取资源(acquire)源码
  • 释放资源(release)源码
  • 内部类-ConditionObject(条件队列的实现)
private static final long serialVersionUID = 7373984972572414691L; // 维护了CLH队列的头节点 private transient volatile AbstractQueuedSynchronizer.Node head; // 维护了CLH队列的尾节点 private transient volatile AbstractQueuedSynchronizer.Node tail; // 临界资源、也表示能有多少线程获取锁 private volatile int state; static final long spinForTimeoutThreshold = 1000L; private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long stateOffset; private static final long headOffset; private static final long tailOffset; private static final long waitStatusOffset; private static final long nextOffset;
// 获取初始值,主要会用来判断是否有资格获取锁 protected final int getState() { return this.state; } // 主要用来设置初始数值 protected final void setState(int var1) { this.state = var1; } // 通过 CAS 设置state的值 protected final boolean compareAndSetState(int var1, int var2) { return unsafe.compareAndSwapInt(this, stateOffset, var1, var2); }

AQS的工作模式分为独占模式和共享模式,记录在节点的信息中。

使用模板方式 => 定义一个操作中算法的骨架

一般实现类只实现其中一种模式:ReentrantLock就实现了独占模式、(例外)ReentrantReadAndWriteLock实现了独占模式和共享模式

为什么线程节点成为了头节点为什么会将节点中的线程信息置为空?

  1. 首先AQS继承了AbstractOwnableSynchronied这个类,这个类就是用来记录当前正在运行的线程的

public abstract class AbstractOwnableSynchronizer implements Serializable { private static final long serialVersionUID = 3737899427754241961L; // 记录了当前线程 private transient Thread exclusiveOwnerThread; protected AbstractOwnableSynchronizer() { } // 这个方法很常见,在之后的ReentrantLock.Sync中的 ****tryAcquire() 方法中很常见 protected final void setExclusiveOwnerThread(Thread var1) { this.exclusiveOwnerThread = var1; } protected final Thread getExclusiveOwnerThread() { return this.exclusiveOwnerThread; } }

Node节点

// 当前节点处于共享模式的标记 static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node(); // 当前节点处于独占模式的标记 static final AbstractQueuedSynchronizer.Node EXCLUSIVE = null; // 当前节点的线程被取消 static final int CANCELLED = 1; // 当前节点的线程处于阻塞状态 + 释放资源后需要唤醒后继节点 static final int SIGNAL = -1; // 当前节点处于等待状态(等待condition唤醒) static final int CONDITION = -2; // 工作在共享锁中、会向后传播、根据资源数量唤起后继节点 static final int PROPAGATE = -3; // 等待状态 = 上面的4个状态 + ‘0’默认创建时候状态(初始状态、正常状态) volatile int waitStatus; // 前驱节点 volatile AbstractQueuedSynchronizer.Node prev; // 后继节点 volatile AbstractQueuedSynchronizer.Node next; // 等待锁的线程 volatile Thread thread; // 等待条件中的下一个节点(Condition中用到) AbstractQueuedSynchronizer.Node nextWaiter;

获取资源

本质上都是对state这个资源变量的修改

acquire()方法

/** 首先执行tryAcquire()方法 => 自己实现AQS时候定义的 => 就是先尝试获取锁并设置ExclusiveOwnerThread为自己 如果第一次tryAcquire失败 先进入addWaiter方法 => 将当前节点插入到队尾 后进入acquireQueued方法 => 用自旋的方式尝试获取锁并判断是否要挂起 */ public final void acquire(int var1) { if (!this.tryAcquire(var1) && this.acquireQueued(this.addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), var1)) { selfInterrupt(); } }

addWaiter()方法

将当前线程插入至队尾,返回在等待队列中的节点(就是处理了它的前驱后继)

private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node var1) { // 新建一个节点 AbstractQueuedSynchronizer.Node var2 = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), var1); // 获取到当前的尾节点 AbstractQueuedSynchronizer.Node var3 = this.tail; // 如果当前尾节点为空,则先直接尝试替换当前的尾节点 if (var3 != null) { // 先将当前节点的前驱设为替换前的尾节点 var2.prev = var3; // 使用CAS尝试替换尾节点并让其指向当前节点 if (this.compareAndSetTail(var3, var2)) { // 到这一步时候tail已经指向了var2即当前节点,那么var3指向的就是原先的tail就是当前节点的前驱,那么var3的后继节点就是现在的tail就是var2 var3.next = var2; return var2; } } // 如果尾节点没有初始化 或者 上面的替换操作失败 this.enq(var2); return var2; }

enq()方法

将节点插入队尾,失败则自旋,直到成功

private AbstractQueuedSynchronizer.Node enq(AbstractQueuedSynchronizer.Node var1) { // while经典自旋操作 while(true) { // 获取到当前的tail节点(因为后面tail会被变成当前节点,故需要提前获取)作为当前节点的前驱节点 // 每次开始新的自旋都要重新获取=>因为前面操作失败说明有线程并发操作对应的tail指向就发生的改变 AbstractQueuedSynchronizer.Node var2 = this.tail; // 如果这个tail没有初始化 => 当前节点是队列创建以来的第一个节点 if (var2 == null) { // 将当前节点设置为头节点 if (this.compareAndSetHead(new AbstractQueuedSynchronizer.Node())) { // 因为就一个节点,那么head和tail自然指向同一个 this.tail = this.head; } } else { // 当前节点的前驱是tail获取到的节点,这也是tail赋值给var2的原因,毕竟后面tail就会被替换成当前节点 var1.prev = var2; // 替换的tail为当前节点 => 就是将当前节点插入到队尾 if (this.compareAndSetTail(var2, var1)) { // 到这一步说明替换(插入)成功,则tail为当前节点,过去tail指向的var2节点则变为了当前节点的前驱节点 var2.next = var1; // 将节点插入队列成功 return var2; } } } }

总结addWaiter()方法

总的来说,addWaiter负责的就是将tryAcquire()方法获取锁失败的线程插入到队列中

acquireQueued()方法

自旋方式获取资源并判断是否需要被挂起(原则上除了唤醒和中断会一直卡在这个方法,即便是挂起状态)

final boolean acquireQueued(AbstractQueuedSynchronizer.Node var1, int var2) { boolean var3 = true; try { // 记录是否被中断过 => 涉及到acquire方法中的selfInterrupt()方法是否会执行 boolean var4 = false; while(true) { // 获取到当前节点的前驱节点 AbstractQueuedSynchronizer.Node var5 = var1.predecessor(); // 如果当前节点是头节点并且获取到了锁(注意tryAcquire方法 =>自己实现AQS时候定义的 => 就是先尝试获取锁并设置ExclusiveOwnerThread为自己) if (var5 == this.head && this.tryAcquire(var2)) { // 这个setHead很有学问,注意看对应的源码 this.setHead(var1); // 既然这个头节点已经没用了,自然要回收,设置为null方便GC检测到并回收 var5.next = null; var3 = false; boolean var6 = var4; //返回是否被中断过 return var6; } // 既然没有获取到锁,自然有一套善后工作 /** shouldParkAfterFailedAcquire()将这个节点放到合适的位置,表现为前驱节点为SIGNAL状态 => SIGNAL状态能够唤醒其后继节点 parkAndCheckInterrupt()就是真正意义上将这个线程节点阻塞了 */ if (shouldParkAfterFailedAcquire(var5, var1) && this.parkAndCheckInterrupt()) { // 记录中断依次,到selfInterrupt()方法执行 var4 = true; } } } finally { // 这个主要是用来应对中断线程的情况=>中断就意为着进入到CANCELLED状态 if (var3) { this.cancelAcquire(var1); } } }

setHead()方法

// 首先要明确的是=>只有当前线程节点已经获取到锁后再会进入到这个设置头节点的方法 => 就是已经记录了独占线程是谁 // tryAcquire方法中调用了setExclusiveThread()方法设置了独占线程 private void setHead(AbstractQueuedSynchronizer.Node var1) { // 设置新的头节点 this.head = var1; // 将这个节点的线程记录置为空 => 因为调用setExclusiveThread方法记录了独占线程 var1.thread = null; // 都已经是头节点了自然没有前驱节点 var1.prev = null; }

shouldParkAfterFailedAcquire()方法

判断当前节点是否应该被挂起

/** var0 对应的是当前节点的前驱节点 var1 对应的是当前节点 */ private static boolean shouldParkAfterFailedAcquire(AbstractQueuedSynchronizer.Node var0, AbstractQueuedSynchronizer.Node var1) { // 获取到前驱节点的状态 => 主要就是希望前驱节点是SIGNAL状态,能够唤醒后继节点 int var2 = var0.waitStatus; // 如果正好是SIGNAL状态则直接返回 if (var2 == -1) { return true; } else { // 如果是CANCALLED状态 => 线程取消了就是没用的线程 => 将当前节点的位置一直提前到不是一个取消了的节点 if (var2 > 0) { do { var1.prev = var0 = var0.prev; } while(var0.waitStatus > 0); var0.next = var1; } else { // 将此时的前驱节点设置为SIGNAL状态 compareAndSetWaitStatus(var0, var2, -1); } // 由于前驱节点不是SIGNAL状态,就返回false => 就表示会再一次自旋尝试获取锁 => 判断是否前驱是头节点啊什么的 return false; } }

parkAndCheckInterrupt()方法

将对应的线程挂起

若确定有必要park,才会执行此方法

private final boolean parkAndCheckInterrupt() { // 挂起线程 LockSupport.park(this); /** 判断是否是因为挂起后被中断唤醒的 => 是则返回true => 同时也意味着放弃争夺锁 => 进入到CANCELLED状态(由finally代码块中的cancelAcquire()方法设置未CANCELLED) => 不是则返回false => 意为着是被唤醒 => 可以抢锁了 */ return Thread.interrupted(); }

selfInterrupt()方法

对当前线程产生一个中断请求。能走到这个方法,说明acquireQueued()返回true,就进行自我中断

// 说明在parkAndCheckInterrupt()中挂起的线程被打断了 => 那就进入到中断状态(自我中断) static void selfInterrupt() { Thread.currentThread().interrupt(); }

acquire的步骤

  1. tryAcquire()尝试获取资源
  2. 如果获取失败,则通过addWaiter(Node.EXCLUSIVE), arg)方法把当前线程添加到等待队列队尾,并标记为独占模式
  3. 插入等待队列后,并没有放弃获取资源,acquireQueued()自旋尝试获取资源。根据前置节点状态状态判断是否应该继续获取资源。如果前驱是头结点,继续尝试获取资源
  4. 在每一次自旋获取资源过程中,失败后调用shouldParkAfterFailedAcquire(Node, Node)检测当前节点是否应该park()。若返回true,则调用parkAndCheckInterrupt()中断当前节点中的线程。若返回false,则接着自旋获取资源。当acquireQueued(Node,int)返回true,则将当前线程中断;false则说明拿到资源了
  5. 在进行是否需要挂起的判断中,如果前置节点是SIGNAL状态,就挂起,返回true。如果前置节点状态为CANCELLED,就一直往前找,直到找到最近的一个处于正常等待状态的节点,并排在它后面,返回false,acquireQueed()接着自旋尝试,回到3)
  6. 前置节点处于其他状态,利用CAS将前置节点状态置为SIGNAL。当前置节点刚释放资源,状态就不是SIGNAL了,导致失败,返回false。但凡返回false,就导致acquireQueed()接着自旋尝试
  7. 最终当tryAcquire(int)返回false,acquireQueued(Node,int)返回true,调用selfInterrupt(),中断当前线程

释放资源

首先调用子类的tryRelease()方法释放锁,然后唤醒后继节点,在唤醒的过程中,需要判断后继节点是否满足情况,如果后继节点不为空且不是作废状态,则唤醒这个后继节点,否则从tail节点向前寻找合适的节点,如果找到,则唤醒

为什么从后继节点作废时候要从tail开始找?

unparkSuccessor方法

private void unparkSuccessor(Node node) { //获取wait状态 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0);// 将等待状态waitStatus设置为初始值0 /** * 若后继结点为空,或状态为CANCEL(已失效),则从后尾部往前遍历找到最前的一个处于正常阻塞状态的结点 * 进行唤醒 */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; // 重点在这里,是从tail开始找的 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread);//唤醒线程 }

高并发下的入队逻辑

private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize //队列为空需要初始化,创建空的头节点 if (compareAndSetHead(new Node())) tail = head; } else { // !!!!可以注意到在执行CAS之前,就将prev这条链连上了,所以虽然高并发但从尾到头这条路是通的 node.prev = t; //set尾部节点 if (compareAndSetTail(t, node)) {//当前节点置为尾部 t.next = node; //前驱节点的next指针指向当前节点 return t; } } } }

原子性问题

在该段方法中,将当前节点置于尾部使用了CAS来保证线程安全,但是请注意:在if语句块中的代码并没有使用任何手段来保证线程安全!

也就是说,在高并发情况下,可能会出现这种情况:

线程A通过CAS进入if语句块之后,发生上下文切换,此时线程B同样执行了该方法,并且执行完毕。然后线程C调用了unparkSuccessor方法

假如是从头到尾的遍历形式,线程A的next指针此时还是null!也就是说,会出现后续节点被漏掉的情况

node.prev = t先于CAS执行,也就是说,你在将当前节点置为尾部之前就已经把前驱节点赋值了,自然不会出现prev=null的情况

release()方法

首先调用子类的tryRelease()方法释放锁,然后唤醒后继节点,在唤醒的过程中,需要判断后继节点是否满足情况,如果后继节点不为空且不是作废状态,则唤醒这个后继节点,否则从tail节点向前寻找合适的节点,如果找到,则唤醒

public final boolean release(int var1) { // 首先释放当前占用的锁 => tryrelease方法 if (this.tryRelease(var1)) { AbstractQueuedSynchronizer.Node var2 = this.head; // 如果当前头节点不是空 // 原则上头节点对应的就是当前线程(即便头节点中存储的线程被清除了)=> 要判断可能是因为有些线程因为非公平锁直接走的后门没有对应的节点 => 等到后面来了个线程请求没有获取到锁进入队列时候head和tail是null创建了一个Node节点为头节点,这个线程对应的节点跟在这个空Node后面 => 从表现上虽然是后创建的Node但也相当于那个走后门拥有锁的线程的所属Node,所以如果出现head为空那就说明没啥竞争出现,就这一个线程 if (var2 != null && var2.waitStatus != 0) { // 寻找需要唤醒的节点对象(如果头节点是取消状态 -> 从tail开始向前遍历一直到head找到唤醒对象) this.unparkSuccessor(var2); } return true; } else { return false; } }

tryrelease()方法 - ReentrantLock类中的方法

protected final boolean tryRelease(int var1) { // 将当前的state进行减一操作 int var2 = this.getState() - var1; // 判断当前操作的线程是否是占有锁的线程 if (Thread.currentThread() != this.getExclusiveOwnerThread()) { throw new IllegalMonitorStateException(); } else { boolean var3 = false; if (var2 == 0) { var3 = true; // 释放锁,占有的线程设为空 this.setExclusiveOwnerThread((Thread)null); } // 设置state资源的值 this.setState(var2); return var3; } }

unparkSuccessor()方法

尝试找到下一位继承人,就是确定下一个获取资源的线程,唤醒指定节点的后继节点

private void unparkSuccessor(AbstractQueuedSynchronizer.Node var1) { // 获取到这个节点的状态(实际上这个就是头节点) int var2 = var1.waitStatus; if (var2 < 0) { compareAndSetWaitStatus(var1, var2, 0); } // 获取到后继节点 AbstractQueuedSynchronizer.Node var3 = var1.next; // 后继节点不存在或者被取消了(不存在原因可能是真的不存在,也能是设置了prev没有设置next(参考从tail遍历的原因)) if (var3 == null || var3.waitStatus > 0) { var3 = null; // 从tail开始遍历到头,找到一个可以唤醒的节点 for(AbstractQueuedSynchronizer.Node var4 = this.tail; var4 != null && var4 != var1; var4 = var4.prev) { if (var4.waitStatus <= 0) { var3 = var4; } } } // 找到了可以唤醒的节点 => 取消挂起 if (var3 != null) { LockSupport.unpark(var3.thread); } }

ReentrantLock源码分析

内部类Sync的实现

abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; /*无参构造*/ abstract void lock(); // 可以看出默认的就是非公平的tryAcquire方法 final boolean nonfairTryAcquire(int var1) { Thread var2 = Thread.currentThread(); int var3 = this.getState(); if (var3 == 0) { if (this.compareAndSetState(0, var1)) { this.setExclusiveOwnerThread(var2); return true; } } else if (var2 == this.getExclusiveOwnerThread()) { int var4 = var3 + var1; if (var4 < 0) { throw new Error("Maximum lock count exceeded"); } this.setState(var4); return true; } return false; } // 通用的释放tryRelease方法 protected final boolean tryRelease(int var1) { int var2 = this.getState() - var1; if (Thread.currentThread() != this.getExclusiveOwnerThread()) { throw new IllegalMonitorStateException(); } else { boolean var3 = false; if (var2 == 0) { var3 = true; this.setExclusiveOwnerThread((Thread)null); } this.setState(var2); return var3; } } protected final boolean isHeldExclusively() { return this.getExclusiveOwnerThread() == Thread.currentThread(); } /*还没细看的一些方法*/ }

NonfairSync类的实现

// 这个对应的是非公平的实现 static final class NonfairSync extends ReentrantLock.Sync { private static final long serialVersionUID = 7316153563782823691L; /*无参构造*/ final void lock() { // 上锁前先抢一下锁试试的意思(毕竟非公平) if (this.compareAndSetState(0, 1)) { this.setExclusiveOwnerThread(Thread.currentThread()); } else { // 没抢到就进入队列咯 不过在addWaiter前还是会tryAcquire()一下(意思就是虽然前一次没有抢到,但在进队列前还要抢一下,就是在非公平下一共抢了两次) => addWaiter() => acquireQueued() this.acquire(1); } } // 由于Sync原本就是非公平的,直接调用父类的即可 protected final boolean tryAcquire(int var1) { return this.nonfairTryAcquire(var1); } }

FairSync类的实现

// 这个对应的是公平的实现 static final class FairSync extends ReentrantLock.Sync { private static final long serialVersionUID = -3000897897090466540L; /*无参构造*/ final void lock() { this.acquire(1); } // 此时父类对应的是非公平的默认实现,所以需要自己写一个 protected final boolean tryAcquire(int var1) { // 获取当前线程 Thread var2 = Thread.currentThread(); int var3 = this.getState(); // 如果当前资源空闲的,就尝试获取 if (var3 == 0) { // 如果队列中有节点,那就不抢 => 毕竟是公平锁嘛,先到先得 if (!this.hasQueuedPredecessors() && this.compareAndSetState(0, var1)) { this.setExclusiveOwnerThread(var2); return true; } // 如果是自己本身,那就是重入的概念了 } else if (var2 == this.getExclusiveOwnerThread()) { int var4 = var3 + var1; if (var4 < 0) { throw new Error("Maximum lock count exceeded"); } this.setState(var4); return true; } // 没获取到锁就返回咯 return false; } }

构造函数源码

private final ReentrantLock.Sync sync; // 未指定就是非公平锁 public ReentrantLock() { this.sync = new ReentrantLock.NonfairSync(); } // 指定了(传参True => 公平锁 / false => 非公平锁) public ReentrantLock(boolean var1) { this.sync = (ReentrantLock.Sync)(var1 ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync()); }

lock()方法源码

// 就直接调用即可 public void lock() { this.sync.lock(); }

unlock()方法源码

// 也是直接调用,不过值得注意的是无论是非公平还是公平的锁都公共一个tryRelease()逻辑 public void unlock() { this.sync.release(1); }

__EOF__

本文作者TPureZY
本文链接https://www.cnblogs.com/TPureZY/p/15944058.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   寺瞳  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示