赞
踩
目录
(一)ReentrantLock与Synchronized特性比较
(二)AQS重要方法(以ReentrantLock关联分析)
干货分享,感谢您的阅读!
AQS全称为AbstractQueuedSynchronizer,是Java中用于构建锁和同步器的框架性组件,它是Java并发包中ReentrantLock、Semaphore、ReentrantReadWriteLock等同步器的基础。AQS的设计思想是,在其内部维护了一个双向队列,用于管理请求锁的线程。当有线程请求锁时,AQS会将其封装成一个Node节点,并加入到等待队列中,线程则会进入阻塞状态。当持有锁的线程释放锁时,AQS会从等待队列中唤醒一个线程来获取锁,从而实现线程的同步和互斥。
AQS的主要特点包括:
AQS的实现被广泛应用于Java并发包中的各种同步器,如ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch等,为Java中的并发编程提供了基础设施。AQS为这些同步器提供了一个统一的基础框架,并且可以让开发人员基于此进行扩展和定制化,避免自己重复实现同步器的底层机制,从而更加专注于业务的实现。
在开发中,我们可以利用AQS提供的同步机制来实现线程的协作和同步,从而达到线程安全的目的。以下是一些常见的开发应用场景:
除此之外,AQS还可以用来实现自定义的同步器,比如读写锁、可重入锁等。在开发中,我们可以根据具体的业务需求和线程安全要求,灵活地使用AQS提供的各种同步机制。
ReentrantLock的可重入性是AQS很好的应用之一,在ReentrantLock里面,不管是公平锁还是非公平锁,都有下面一段逻辑
- // java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
-
- if (c == 0) {
- if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
- setExclusiveOwnerThread(current);
- return true;
- }
- } else if (current == getExclusiveOwnerThread()) {
- int nextc = c + acquires;
- if (nextc < 0)
- throw new Error("Maximum lock count exceeded");
- setState(nextc);
- return true;
- }
-
-
- // java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
-
- if (c == 0) {
- if (compareAndSetState(0, acquires)){
- setExclusiveOwnerThread(current);
- return true;
- }
- } else if (current == getExclusiveOwnerThread()) {
- int nextc = c + acquires;
- if (nextc < 0) // overflow
- throw new Error("Maximum lock count exceeded");
- setState(nextc);
- return true;
- }
可以看到,有一个同步状态State来控制整体可重入的情况。State是Volatile修饰的,用于保证一定的可见性和有序性。
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- private volatile int state;
State这个字段主要的过程:
各公司封装各有不同,我们这里实现一个同步工具基本代码,极简方式:
- package org.zyf.javabasic.thread.toolstest.zyf;
-
- import java.util.concurrent.locks.AbstractQueuedSynchronizer;
-
- /**
- * @author yanfengzhang
- * @description
- * @date 2020/5/2 10:48
- */
- public class ZYFLock {
- private static class Sync extends AbstractQueuedSynchronizer {
- @Override
- protected boolean tryAcquire(int arg) {
- return compareAndSetState(0, 1);
- }
-
- @Override
- protected boolean tryRelease(int arg) {
- setState(0);
- return true;
- }
-
- @Override
- protected boolean isHeldExclusively() {
- return getState() == 1;
- }
- }
-
- private Sync sync = new Sync();
-
- public void lock() {
- sync.acquire(1);
- }
-
- public void unlock() {
- sync.release(1);
- }
- }
测试如下:
- package org.zyf.javabasic.thread.toolstest.zyf;
-
- /**
- * @author yanfengzhang
- * @description
- * @date 2020/5/2 10:50
- */
- public class ZYFLockTest {
- static int count = 0;
- static ZYFLock zyfLock = new ZYFLock();
-
- public static void main(String[] args) throws InterruptedException {
-
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- try {
- zyfLock.lock();
- for (int i = 0; i < 10000; i++) {
- count++;
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- zyfLock.unlock();
- }
-
- }
- };
- Thread thread1 = new Thread(runnable);
- Thread thread2 = new Thread(runnable);
- thread1.start();
- thread2.start();
- thread1.join();
- thread2.join();
- //输入结果为20000
- System.out.println(count);
- }
- }
- // **************************Synchronized的使用方式**************************
- // 1.用于代码块
- synchronized (this) {}
- // 2.用于对象
- synchronized (object) {}
- // 3.用于方法
- public synchronized void test () {}
- // 4.可重入
- for (int i = 0; i < 100; i++) {
- synchronized (this) {}
- }
- // **************************ReentrantLock的使用方式**************************
- public void test () throw Exception {
- // 1.初始化选择公平锁、非公平锁
- ReentrantLock lock = new ReentrantLock(true);
- // 2.可用于代码块
- lock.lock();
- try {
- try {
- // 3.支持多种加锁方式,比较灵活; 具有可重入特性
- if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ }
- } finally {
- // 4.手动释放锁
- lock.unlock()
- }
- } finally {
- lock.unlock();
- }
- }
ReentrantLock意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁,其特性如下(与Synchronized进行比较):
对比内容 | ReentrantLock | Synchronized |
锁实现机制 | 依赖AQS | 监视器模式 |
灵活性 | 支持响应中断、超时、尝试获取锁 | 不灵活 |
释放形式 | 必须显示的调用unlock()释放锁 | 自动释放监视器 |
锁类型 | 公平锁&非公平锁 | 非公平锁 |
条件队列 | 可关联多个条件队列 | 关联一个条件队列 |
可重入性 | 可重入 | 可重入 |
ReentrantLock支持公平锁和非公平锁,并且ReentrantLock的底层就是由AQS来实现的,着重从这两者的加锁过程来理解与AQS之间的关系。
加锁流程代码如下:
- // java.util.concurrent.locks.ReentrantLock#NonfairSync
-
- // 非公平锁
- static final class NonfairSync extends Sync {
- ...
- final void lock() {
- if (compareAndSetState(0, 1))
- setExclusiveOwnerThread(Thread.currentThread());
- else
- acquire(1);
- }
- ...
- }
-
- // java.util.concurrent.locks.ReentrantLock#FairSync
-
- static final class FairSync extends Sync {
- ...
- final void lock() {
- acquire(1);
- }
- ...
- }
这块代码的含义的重点在于为获取锁失败,则进入Acquire方法进行后续处理。结合公平锁和非公平锁的加锁流程,虽然流程上有一定的不同,但是都调用了Acquire方法,而Acquire方法是FairSync和UnfairSync的父类AQS中的核心方法。
通过下面的架构图来整体了解一下AQS框架,有颜色的为Method,无颜色的为Attribution,总的来说,AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据。
当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。
AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列(CLH:Craig、Landin and Hagersten队列,是单向链表)的变体实现的,将暂时获取不到锁的线程加入到队列中(AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配)。
主要原理图如下,AQS使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改。
AQS中最基本的数据结构——Node(即为上面CLH变体队列中的节点),其基本源码如下:
- static final class Node {
- // 标识节点当前在共享模式下
- static final Node SHARED = new Node();
- // 标识节点当前在独占模式下
- static final Node EXCLUSIVE = null;
-
- // ======== 下面的几个int常量是给waitStatus用的 ===========
- /** waitStatus value to indicate thread has cancelled */
- // 代码此线程取消了争抢这个锁
- static final int CANCELLED = 1;
- /** waitStatus value to indicate successor's thread needs unparking */
- // 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒
- static final int SIGNAL = -1;
- /** waitStatus value to indicate thread is waiting on condition */
- // 本文不分析condition,所以略过吧,下一篇文章会介绍这个
- static final int CONDITION = -2;
- /**
- * waitStatus value to indicate the next acquireShared should
- * unconditionally propagate
- */
- // 同样的不分析,略过吧
- static final int PROPAGATE = -3;
- // =====================================================
-
-
- // 取值为上面的1、-1、-2、-3,或者0(以后会讲到)
- // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待,
- // ps: 半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。
- volatile int waitStatus;
- // 前驱节点的引用
- volatile Node prev;
- // 后继节点的引用
- volatile Node next;
- // 这个就是线程本尊
- volatile Thread thread;
- //指向下一个处于CONDITION状态的节点
- Node nextWaiter;
- //返回前驱节点,没有的话抛出npe
- final Node predecessor() throws NullPointerException {
- Node p = prev;
- if (p == null)
- throw new NullPointerException();
- else
- return p;
- }
- }
根据结构代码,基本含义展示如下:
其中waitStatus有下面几个枚举值如下:
AQS中维护了一个名为state的字段,意为同步状态,是由Volatile修饰的,用于展示当前临界资源的获锁情况。
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- private volatile int state;
下面提供了几个访问这个字段的方法:
- //获取State的值
- protected final int getState()
- //设置State的值
- protected final void setState(int newState)
- //使用CAS方式更新State
- protected final boolean compareAndSetState(int expect, int update)
这几个方法都是final修饰的,说明子类中无法重写它们。我们可以通过修改State字段表示的同步状态来实现多线程的独占模式和共享模式(加锁过程)(对于我们自定义的同步工具,需要自定义获取同步状态和释放状态的方式)。
从架构图中可以得知,AQS提供了大量用于自定义同步器实现的Protected方法。自定义同步器实现的相关方法也只是为了通过修改State字段来实现多线程的独占模式或者共享模式。部分展示如下:
一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。ReentrantLock是独占锁,所以实现了tryAcquire-tryRelease。
以非公平锁为例,这里主要阐述一下非公平锁与AQS之间方法的关联之处,基本流程图如下:
以非公平锁为例,总结如下:
ReentrantLock公平锁和非公平锁在底层是相同的,以非公平锁为例分析。基本代码罗列如下:
- // java.util.concurrent.locks.ReentrantLock
-
- static final class NonfairSync extends Sync {
- ...
- final void lock() {
- if (compareAndSetState(0, 1))
- setExclusiveOwnerThread(Thread.currentThread());
- else
- acquire(1);
- }
- ...
- }
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- public final void acquire(int arg) {
- if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
- }
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- protected boolean tryAcquire(int arg) {
- throw new UnsupportedOperationException();
- }
可以看出,这里只是AQS的简单实现,具体获取锁的实现方法是由各自的公平锁和非公平锁单独实现的(以ReentrantLock为例)。如果该方法返回了True,则说明当前线程获取锁成功,就不用往后执行了;如果获取失败,就需要加入到等待队列中。
当执行Acquire(1)时,会通过tryAcquire获取锁。在这种情况下,如果获取锁失败,就会调用addWaiter加入到等待队列中去。获取锁失败后,会执行addWaiter(Node.EXCLUSIVE)加入等待队列,具体实现方法如下:
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- private Node addWaiter(Node mode) {
- Node node = new Node(Thread.currentThread(), mode);
- // Try the fast path of enq; backup to full enq on failure
- Node pred = tail;
- if (pred != null) {
- node.prev = pred;
- if (compareAndSetTail(pred, node)) {
- pred.next = node;
- return node;
- }
- }
- enq(node);
- return node;
- }
- private final boolean compareAndSetTail(Node expect, Node update) {
- return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
- }
-
-
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- static {
- try {
- stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
- headOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
- tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
- waitStatusOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("waitStatus"));
- nextOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("next"));
- } catch (Exception ex) {
- throw new Error(ex);
- }
- }
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- private Node enq(final Node node) {
- for (;;) {
- Node t = tail;
- if (t == null) { // Must initialize
- if (compareAndSetHead(new Node()))
- tail = head;
- } else {
- node.prev = t;
- if (compareAndSetTail(t, node)) {
- t.next = node;
- return t;
- }
- }
- }
- }
主要的流程如下:
一个线程获取锁失败了,被放入等待队列,acquireQueued会把放入队列中的线程不断去获取锁,直到获取成功或者不再需要获取(中断)。
从“何时出队列”和“如何出队?”两个方向来分析一下acquireQueued源码:
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- final boolean acquireQueued(final Node node, int arg) {
- // 标记是否成功拿到资源
- boolean failed = true;
- try {
- // 标记等待过程中是否中断过
- boolean interrupted = false;
- // 开始自旋,要么获取锁,要么中断
- for (;;) {
- // 获取当前节点的前驱节点
- final Node p = node.predecessor();
- // 如果p是头结点,说明当前节点在真实数据队列的首部,就尝试获取锁(别忘了头结点是虚节点)
- if (p == head && tryAcquire(arg)) {
- // 获取锁成功,头指针移动到当前node
- setHead(node);
- p.next = null; // help GC
- failed = false;
- return interrupted;
- }
- // 说明p为头节点且当前没有获取到锁(可能是非公平锁被抢占了)或者是p不为头结点,这个时候就要判断当前node是否要被阻塞(被阻塞条件:前驱节点的waitStatus为-1),防止无限循环浪费资源。具体两个方法下面细细分析
- if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
- interrupted = true;
- }
- } finally {
- if (failed)
- cancelAcquire(node);
- }
- }
-
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
- // setHead方法是把当前节点置为虚节点,但并没有修改waitStatus,因为它是一直需要用的数据。
- private void setHead(Node node) {
- head = node;
- node.thread = null;
- node.prev = null;
- }
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- // 靠前驱节点判断当前线程是否应该被阻塞
- private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
- // 获取头结点的节点状态
- int ws = pred.waitStatus;
- // 说明头结点处于唤醒状态
- if (ws == Node.SIGNAL)
- return true;
- // 通过枚举值我们知道waitStatus>0是取消状态
- if (ws > 0) {
- do {
- // 循环向前查找取消节点,把取消节点从队列中剔除
- node.prev = pred = pred.prev;
- } while (pred.waitStatus > 0);
- pred.next = node;
- } else {
- // 设置前任节点等待状态为SIGNAL
- compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
- }
- return false;
- }
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
- // parkAndCheckInterrupt主要用于挂起当前线程,阻塞调用栈,返回当前线程的中断状态。
- private final boolean parkAndCheckInterrupt() {
- LockSupport.park(this);
- return Thread.interrupted();
- }
上述方法的流程图如下:
从上图可以看出,跳出当前循环的条件是当“前置节点是头结点,且当前线程获取锁成功”。为了防止因死循环导致CPU资源被浪费,我们会判断前置节点的状态来决定是否要将当前线程挂起,具体挂起流程用流程图表示如下(shouldParkAfterFailedAcquire流程):
对acquireQueued方法代码展开分析
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- final boolean acquireQueued(final Node node, int arg) {
- boolean failed = true;
- try {
- ...
- for (;;) {
- final Node p = node.predecessor();
- if (p == head && tryAcquire(arg)) {
- ...
- failed = false;
- ...
- }
- ...
- } finally {
- if (failed)
- cancelAcquire(node);
- }
- }
-
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
- // 通过cancelAcquire方法,将Node的状态标记为CANCELLED
- private void cancelAcquire(Node node) {
- // 将无效节点过滤
- if (node == null)
- return;
- // 设置该节点不关联任何线程,也就是虚节点
- node.thread = null;
- Node pred = node.prev;
- // 通过前驱节点,跳过取消状态的node
- while (pred.waitStatus > 0)
- node.prev = pred = pred.prev;
- // 获取过滤后的前驱节点的后继节点
- Node predNext = pred.next;
- // 把当前node的状态设置为CANCELLED
- node.waitStatus = Node.CANCELLED;
- // 如果当前节点是尾节点,将从后往前的第一个非取消状态的节点设置为尾节点
- // 更新失败的话,则进入else,如果更新成功,将tail的后继节点设置为null
- if (node == tail && compareAndSetTail(node, pred)) {
- compareAndSetNext(pred, predNext, null);
- } else {
- int ws;
- // 如果当前节点不是head的后继节点,1:判断当前节点前驱节点的是否为SIGNAL,2:如果不是,则把前驱节点设置为SINGAL看是否成功
- // 如果1和2中有一个为true,再判断当前节点的线程是否为null
- // 如果上述条件都满足,把当前节点的前驱节点的后继指针指向当前节点的后继节点
- if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) {
- Node next = node.next;
- if (next != null && next.waitStatus <= 0)
- compareAndSetNext(pred, predNext, next);
- } else {
- // 如果当前节点是head的后继节点,或者上述条件不满足,那就唤醒当前节点的后继节点
- unparkSuccessor(node);
- }
- node.next = node; // help GC
- }
- }
当前的流程:
由于ReentrantLock在解锁的时候,并不区分公平锁和非公平锁,所以我们直接看解锁的源码展开分析:
- // java.util.concurrent.locks.ReentrantLock
-
- public void unlock() {
- sync.release(1);
- }
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- public final boolean release(int arg) {
- if (tryRelease(arg)) {
- Node h = head;
- if (h != null && h.waitStatus != 0)
- unparkSuccessor(h);
- return true;
- }
- return false;
- }
-
- // java.util.concurrent.locks.ReentrantLock.Sync
-
- // 方法返回当前锁是不是没有被线程持有
- protected final boolean tryRelease(int releases) {
- // 减少可重入次数
- int c = getState() - releases;
- // 当前线程不是持有锁的线程,抛出异常
- if (Thread.currentThread() != getExclusiveOwnerThread())
- throw new IllegalMonitorStateException();
- boolean free = false;
- // 如果持有线程全部释放,将当前独占锁所有线程设置为null,并更新state
- if (c == 0) {
- free = true;
- setExclusiveOwnerThread(null);
- }
- setState(c);
- return free;
- }
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- public final boolean release(int arg) {
- // 上边自定义的tryRelease如果返回true,说明该锁没有被任何线程持有
- if (tryRelease(arg)) {
- // 获取头结点
- Node h = head;
- // 头结点不为空并且头结点的waitStatus不是初始化节点情况,解除线程挂起状态
- if (h != null && h.waitStatus != 0)
- unparkSuccessor(h);
- return true;
- }
- return false;
- }
-
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- private void unparkSuccessor(Node node) {
- // 获取头结点waitStatus
- int ws = node.waitStatus;
- if (ws < 0)
- compareAndSetWaitStatus(node, ws, 0);
- // 获取当前节点的下一个节点
- Node s = node.next;
- // 如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点
- if (s == null || s.waitStatus > 0) {
- s = null;
- // 就从尾部节点开始找,到队首,找到队列第一个waitStatus<0的节点。
- for (Node t = tail; t != null && t != node; t = t.prev)
- if (t.waitStatus <= 0)
- s = t;
- }
- // 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点unpark
- if (s != null)
- LockSupport.unpark(s.thread);
- }
综上所述,如果是从前往后找,由于极端情况下入队的非原子操作和CANCELLED节点产生过程中断开Next指针的操作,可能会导致无法遍历所有的节点。所以,唤醒对应的线程后,对应的线程就会继续往下执行。
唤醒后,会执行return Thread.interrupted();这个函数返回的是当前执行线程的中断状态,并清除。
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- private final boolean parkAndCheckInterrupt() {
- LockSupport.park(this);
- return Thread.interrupted();
- }
回到acquireQueued代码,当parkAndCheckInterrupt返回True或者False的时候,interrupted的值不同,但都会执行下次循环。如果这个时候获取锁成功,就会把当前interrupted返回。
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- final boolean acquireQueued(final Node node, int arg) {
- boolean failed = true;
- try {
- boolean interrupted = false;
- for (;;) {
- final Node p = node.predecessor();
- if (p == head && tryAcquire(arg)) {
- setHead(node);
- p.next = null; // help GC
- failed = false;
- return interrupted;
- }
- if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
- interrupted = true;
- }
- } finally {
- if (failed)
- cancelAcquire(node);
- }
- }
如果acquireQueued为True,就会执行selfInterrupt方法。
- // java.util.concurrent.locks.AbstractQueuedSynchronizer
-
- static void selfInterrupt() {
- Thread.currentThread().interrupt();
- }
该方法其实是为了中断线程。
AQS的原理可以简单概括为以下几点:
AQS的实现原理其实就是上述几个核心点的实现。对于AQS的使用者来说,只需要了解AQS的使用方法即可。而对于AQS的实现者来说,需要深入理解AQS的实现原理,并且了解AQS在不同场景下的具体应用,以便于在需要的时候能够根据具体需求来实现自定义的同步器。
1.从ReentrantLock的实现看AQS的原理及应用 - 美团技术团队
2.一行一行源码分析清楚AbstractQueuedSynchronizer_Javadoop
3.Java Concurrency: Understanding AQS: 这是一篇比较全面的介绍AQS的文章,包括AQS的设计原理、使用场景以及实现原理等方面的内容。链接:https://dzone.com/articles/java-concurrency-understanding-aqs
4.AbstractQueuedSynchronizer: 这是AQS官方文档,其中包含了AQS的详细说明和使用方法,可以作为AQS的参考手册。链接:AbstractQueuedSynchronizer (Java SE 11 & JDK 11 )
5.AQS学习笔记: 这是一篇比较系统地介绍AQS的博客文章,从AQS的基本概念、内部实现以及使用方法等方面进行了详细的阐述。链接:https://www.cnblogs.com/zhangxiaoliang/p/6208458.html
6.Java并发编程之AQS详解: 这是一篇非常详细的介绍AQS的博客文章,从AQS的设计原理、使用场景、内部实现以及ReentrantLock和ReentrantReadWriteLock的实现等方面进行了深入的讲解。链接:https://www.cnblogs.com/nullzx/p/7691638.html
7.Java 并发编程之 AQS 原理: 这是一篇非常清晰易懂的介绍AQS的博客文章,从AQS的基本概念、内部实现以及使用方法等方面进行了讲解,并通过图文并茂的方式帮助读者更好地理解AQS的实现原理。链接:https://segmentfault.com/a/1190000017199508
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。