彻底搞懂 - 从源码看JUC的基石: AQS

JUC框架 源码解析系列文章目录 JDK8 前言 笔者在接下来这段时间将对JUC框架内的重点内容进行深度解析,由于内容颇多,需要分成多篇文章,所以在这里列一个目录,方便大家翻阅。 目录 JUC框架的基础是CAS和自旋,而CAS则是利用Unsafe类提供的CAS操作,而原子类则依靠于CAS和自旋。下面几篇文章从源码分析JUC框架内的几个重要的原子类。 JUC AtomicInteger源码解析 JDK8 JUC AtomicIntegerArray源码解析 JDK8 JUC AtomicStampedReference源码解析 JDK8 AQS(A 阅读详情

AQS(AbstractQueuedSynchronizer)抽象队列式同步器,从它的名字中,我们可以看出AQS本身是一个同步器,也就是发生资源竞争时,用来同步的工具。它的内部使用了队列来进行实现,而之所以叫“抽象”,是因为AQS只是一个“框架”,它提供了当发生资源竞争时,竞争失败的线程入队、出队等的方式,但没有描述竞争的具体细节,这些交由其子类来按需具体实现,AQS在JUC中许多大名鼎鼎的类中得到应用,例如ReentrantLock,CountDownLatch,Semaphore等等,可以说,AQS是JUC的基石,搞懂了AQS,就能轻松理解JUC中各种锁的api。

一. 概念

AQS的模型:
在这里插入图片描述

AQS在内部维护定义了一个内部类Node,用它来构建一个FIFO的双向链表,分别有Head和Tail来指向链表头尾,基于这样一套数据结构,形成了基于逻辑队列非线程饥饿的一种自旋公平锁,称为CLH锁。(由于是 Craig、Landin 和 Hagersten三位大佬的发明,因此命名为CLH锁,CLH锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋)。如果说AQS是JUC的基石,那么CLH就是AQS的基石。

AQS将资源的共享方式定义为两种:1.Exclusive 独占,即只有一个线程可以访问资源。2. Share 共享,即允许同时有X个线程访问资源。AQS使用一个被volatile修饰的int变量state来代表资源。

针对两种共享方式,子类需要继承AQS,并分别实现不同的接口,主要是如下几个:
Exclusive:
tryAcquire: 尝试获取资源,成功返回true,失败返回false。
tryRelease: 尝试释放资源,成功返回true,失败返回false。

Share:
tryAcquireShared: 尝试获取资源,返回负数表示失败,0表示成功,但没有资源可用,正数表示成功,而且有剩余资源。
tryReleaseShared: 尝试释放资源,如果释放后允许唤醒后需等待的线程,则返回true,否则返回false。

以上四个方法,都有一个int参数,AQS没有明确说明这个参数的含义,可以由子类来定义。一般来讲,对于一个继承AQS的锁(同步器)而言,要么实现独占锁,要么实现共享锁,但也有同时实现这两种的,如ReentrantReadWriteLock。为了减轻开发者不必要的工作,上述的四个方法都没有设计成抽象方法,也就是说子类只需要按需实现即可,对于子类未实现,却又被调用了的方法,AQS默认会抛出一个UnsupportedOperationException异常。

典型的独占锁是ReentrantLock,state初始时默认为0,表示资源还没有被获取,当有一个线程调用lock时,lock底层会调用tryAcquire,即独占该资源,并将state + 1,此后这个线程可以重复lock,state也会递增,只有当这个线程调用unlock(底层是tryRelease)至state为0时,其他线程才有机会获得这个资源,这就是可重入的概念,所以ReentrantLock称为可重入锁,它的本质仍是一个独占锁。
下边,我们分别从独占模式和共享模式来描述AQS的执行原理。

二. 独占模式

  1. acquire

    acquire是独占模式下,AQS用来尝试获取资源的方法,可以想象的是,它的实现一定是调用了上文提到的tryAcquire:

	public final void acquire(int arg) {
    if(!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

这段代码的主要流程与含义如下:
1.调用tryAcquire尝试获取资源,如果获取成功,这个方法会直接返回。
2.如果获取失败,则首先调用addWaiter,以独占模式将当前线程包装成一个Node,将它放置到CLH的末尾。
3.再调用acquireQueued,不断自旋尝试获取资源,直到获取成功,当获取成功时,会返回false,此时整个方法也会直接返回。但当自旋过程中发生线程中断时,不会立即响应中断,而是在获取资源成功后,返回一个true,此时selfInterrupt被激活,自行补一个中断。

下面我们细节看一下上边用到的addWaiter,acquireQueued方法:
addWaiter:

	private Node addWaiter(Node mode)
	    {
	       Node node = new Node(Thread.currentThread(), mode);
	
	        Node pred = tail;
	        if (pred != null)
	        {
	            node.prev = pred;
	            if (compareAndSetTail(pred, node))
	            {
	                pred.next = node;
	                return node;
	            }
	        }
	
	        enq(node);
	        return node;
	    }

这里首先将当前线程按照执行模式构造成了一个Node,然后先尝试通过快速的方式将Node加入队尾,当快速加入失败后,则会调用enq方法进行加入,最后返回当前线程所在的Node。下面看enq方法的实现:

	private Node enq(final Node node)
	    {
	        for (;;)
	        {
	            Node t = tail;
	            if (t == null)
	            { 
		        if (compareAndSetHead(new Node()))
	                    tail = head;
	            }
	            else
	            {
	                node.prev = t;
	                if (compareAndSetTail(t, node))
	                {
	                    t.next = node;
	                    return t;
	                }
	            }
	        }
	    }

这是一段非常经典的用法,即CAS自旋volitale变量,首先判断CLH的tail是否为null,如果是null就初始化一个进去。这样做,是因为在AQS中,需要依赖前驱节点将自己唤醒,如果没有前驱,当前节点会永远无法唤醒。再次自旋时,就可以将当前Node放置在新创建的tail后,为防止高并发下tail可能变化的问题,这里通过compareAndSetTail先尝试将Node加入tail后,如果失败了,意味着有线程抢先变化了tail,下一次自旋会再次获取新的tail,再次尝试加入队尾,直到成功。类似的用法可以参考AtomicInteger.getAndIncrement()。compareAndSetTail是Unsafe类里提供的一个方法,底层是native函数,通过CPU原语实现的CAS操作。

通过以上步骤,我们的线程由于没有拿到资源,已经被包装成一 个Node加入了CLH的尾部,下边要做的事就是等待前置线程释放资源、自己被唤醒了,即acquireQueued方法做的事:

	final boolean acquireQueued(final Node node, int arg)
	    {
	        boolean failed = true;// 标记是否成功拿到资源
	        try
	        {
	            boolean interrupted = false;// 标记等待过程中是否被中断过
	
	            for (;;)
	            {
	                final Node p = node.predecessor();// 拿到前驱
	                // 如果前驱是head,即该结点已成老二,那么便可以尝试获取资源(因为此时head可能已经释放了资源)。
	                if (p == head && tryAcquire(arg))
	                {
	                    setHead(node);// 拿到资源后,将head指向该结点,同时会将node.prev置为null。
	                    p.next = null; // 再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了
	                    failed = false;
	                    return interrupted;// 返回等待过程中是否被中断过
	                }
	
	                // 如果自己可以休息了,就进入waiting状态,直到被unpark()
	                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
	                    interrupted = true;// 如果等待过程中被中断过,就将interrupted标记为true
	            }
	        }
	        finally
	        {
	            if (failed)
	                cancelAcquire(node);
	        }
	    }

在梳理整个流程前,我们还应该了解下shouldParkAfterFailedAcquire方法和parkAndCheckInterrupt方法:

	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)
	    {
	        int ws = pred.waitStatus;// 拿到前驱的状态
	        if (ws == Node.SIGNAL)
	            // 如果前驱已经是SIGNAL状态,那就可以安心休息了
	            return true;
	        if (ws > 0)
	        {
	            /*
	             * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
	             * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被GC
	             */
	            do
	            {
	                node.prev = pred = pred.prev;
	            }
	            while (pred.waitStatus > 0);
	            pred.next = node;
	        }
	        else
	        {
	            // 如果前驱正常,那就把前驱的状态设置成SIGNAL。有可能失败,因为前驱有可能刚释放完
	
	            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
	        }
	        return false;
	    }
   private final boolean parkAndCheckInterrupt()
   {
       LockSupport.park(this);// 调用park()使线程进入waiting状态
       return Thread.interrupted();// 如果被唤醒,查看自己是不是被中断的。
   }

可见,这两个方法的作用就是向前寻找一个最近的、状态为SIGNAL的节点,将自己排在它的后边,然后将当前线程置为等待状态,如果被唤醒,会确认下是不是被中断的。关于这个状态,我们目前只需要知道,Node有四种状态,当当前Node的状态为SIGNAL,在他释放资源时,会唤醒其最近的、未放弃的一个后续节点(unpark),这意味着排队中的节点,是可能因为各种原因放弃的,但他们仍在队伍中。

看完这两个方法,我们就可以梳理acquireQueued的流程了:
1.调用tryAcquire尝试获取资源,成功则就直接返回,不成功则调用addWaiter将线程包装成一个Node排在CLH队尾。
2.调用acquireQueued在CLH中找到一个最近的安全点(即状态为SIGNAL的节点),把自己排在它后边,然后将线程状态置为waiting,即开始休息。
3.一旦线程被唤醒(排到第二名了,或者被中断了),就尝试去竞争资源,获取到资源后才会返回。如果整个等待过程中线程被中断过,则返回true,反之返回false。
4.如果线程等待过程中被中断过,拿到资源后会把中断补上。

梳理下acquire整个流程:

实际上,这也正是ReentrantLock.lock()的流程,整个lock函数,其实就是一个acquire(1)。

  1. release
    release是AQS中用于释放资源的操作,他是acquire的反操作,如果资源彻底被释放了(state = 0)它会唤醒后继节点。
    下边是release源码:
	public final boolean release(int arg)
	    {
	        if (tryRelease(arg))
	        {
	            Node h = head;// 找到头结点
	            if (h != null && h.waitStatus != 0)
	                unparkSuccessor(h);// 唤醒等待队列里的下一个线程
	            return true;
	        }
	        return false;
	    }

首先通过调用tryRelease来实现释放资源,如果返回true,则认为释放成功,尝试唤醒后续节点。需要注意的是,如果子类是可重入锁,那么一定要确认state = 0时才能返回true!否则就会出现,明明可重入锁还没有完全释放,后续节点却被唤醒了的情况。
这里我们唯一需要看细节的,是unparkSuccessor方法,它用于在当前节点释放资源后,唤醒后续节点:

	private void unparkSuccessor(Node node)
	    {
	        // node一般为当前线程所在的结点。
	        int ws = node.waitStatus;
	        if (ws < 0)// 置零当前线程所在的结点状态,允许失败。
	            compareAndSetWaitStatus(node, ws, 0);
	
	        Node s = node.next;// 找到下一个需要唤醒的结点s
	        if (s == null || s.waitStatus > 0)
	        {// 如果为空或已取消
	            s = null;
	            for (Node t = tail; t != null && t != node; t = t.prev)
	                if (t.waitStatus <= 0)// <=0的结点,都是还有效的结点。
	                    s = t;
	        }
	        if (s != null)
	            LockSupport.unpark(s.thread);// 唤醒
	    }

这里的逻辑并不复杂,就是用unpark唤醒最前边的有效节点,需要注意的是,它是从tail开始向前寻找的,直到找到那个离当前节点最近的、有效的节点。被唤醒以后,就会继续走acquireQueued的逻辑了,一般来讲,这个有效节点就是当前节点的next,如果真的出现中间有无效节点,也没关系,有效节点被唤醒后,会再次尝试向前找安全点,就会把自己放置到当前节点后,下一轮自旋时,就会被顺利激活。

三.共享模式

1.acquireShared
这是共享模式下,用于获取资源的方法,获取成功则直接返回,失败则进入队列:

		    public final void acquireShared(int arg)
		    {
		        if (tryAcquireShared(arg) < 0)
		            doAcquireShared(arg);
		    }

tryAcquireShared同样是需要子类自己实现的,但这里AQS其实定义好了返回值的语意,负数代表失败,0代表获取成功但没有剩余资源,正数代表成功且有剩余资源。这里我们唯一需要看的是doAcquireShared,它对应独占模式下的acquireQueued方法。

		private void doAcquireShared(int arg)
		    {
		        final Node node = addWaiter(Node.SHARED);// 加入队列尾部
		        boolean failed = true;// 是否成功标志
		        try
		        {
		            boolean interrupted = false;// 等待过程中是否被中断过的标志
		            for (;;)
		            {
		                final Node p = node.predecessor();// 前驱
		                if (p == head)
		                {// 如果到head的下一个,因为head是拿到资源的线程,此时node被唤醒,很可能是head用完资源来唤醒自己的
		                    int r = tryAcquireShared(arg);// 尝试获取资源
		                    if (r >= 0)
		                    {// 成功
		                        setHeadAndPropagate(node, r);// 将head指向自己,还有剩余资源可以再唤醒之后的线程
		                        p.next = null; // help GC
		                        if (interrupted)// 如果等待过程中被打断过,此时将中断补上。
		                            selfInterrupt();
		                        failed = false;
		                        return;
		                    }
		                }
		
		                // 判断状态,寻找安全点,进入waiting状态,等着被unpark()或interrupt()
		                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
		                    interrupted = true;
		            }
		        }
		        finally
		        {
		            if (failed)
		                cancelAcquire(node);
		        }
		    }
		
		可以看到和acquireQueued区别不大,只有两点差异:1.在竞争资源时,会调用setHeadAndPropagate,唤醒自己后,如果还有剩余资源,则会继续唤醒后续节点。2.处理中断的位置与acquireQueued不同。
		我们重点看下setHeadAndPropagate函数:
		
		private void setHeadAndPropagate(Node node, int propagate)
		    {
		        Node h = head;
		        setHead(node);// head指向自己
		        // 如果还有剩余量,继续唤醒下一个线程
		        if (propagate > 0 || h == null || h.waitStatus < 0)
		        {
		            Node s = node.next;
		            if (s == null || s.isShared())
		                doReleaseShared();
		        }
		    }

可以看出,流程上是先唤醒自己,再唤醒后续节点,也就是说,如果当前剩余资源不足以唤醒自己,即便能够满足后续线程使用,也不会跳过当前线程,而是严格按照FIFO。doReleaseShared方法,则是唤醒后继节点的方法,我们把它放在releaseShared方法中讲。

2.releaseShared
这是共享模式下释放资源的方法,是acquireShared的反操作:

		public final boolean releaseShared(int arg)
		    {
		        if (tryReleaseShared(arg))
		        {// 尝试释放资源
		            doReleaseShared();// 唤醒后继结点
		            return true;
		        }
		        return false;
		    }

同样的逻辑,先调用子类的tryReleaseShared尝试释放资源,释放成功后,调用doReleaseShared唤醒后继节点。
跟独占模式下的release()相似,但有一点稍微需要注意:独占模式下的tryRelease()在完全释放掉资源(state=0)后,才会返回true去唤醒其他线程,这主要是基于独占下可重入的考量;而共享模式下的releaseShared()则没有这种要求,共享模式实质就是控制一定量的线程并发执行,那么拥有资源的线程在释放掉部分资源时就可以唤醒后继等待结点。例如,资源总量是13,A(5)和B(7)分别获取到资源并发运行,C(4)来时只剩1个资源就需要等待。A在运行过程中释放掉2个资源量,然后tryReleaseShared(2)返回true唤醒C,C一看只有3个仍不够继续等待;随后B又释放2个,tryReleaseShared(2)返回true唤醒C,C一看有5个够自己用了,然后C就可以跟A和B一起运行。
但这也不是绝对的,如ReentrantReadWriteLock中读锁的tryReleaseShared()只有在完全释放掉资源(state=0)才返回true,所以自定义同步器可以根据需要决定tryReleaseShared()的返回值。
下边看下doReleaseShared细节:

		private void doReleaseShared()
		    {
		        for (;;)
		        {
		            Node h = head;
		            if (h != null && h != tail)
		            {
		                int ws = h.waitStatus;
		                if (ws == Node.SIGNAL)
		                {
		                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
		                        continue;
		                    unparkSuccessor(h);// 唤醒后继
		                }
		                else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
		                    continue;
		            }
		            if (h == head)// head发生变化
		                break;
		        }
		    }

与独占模式差不多。

JUC-AQS】JDK17 AQS源码分析 在学习阳哥的的时候,阳哥讲AQS用的是JDK8,我用的是JDK17,想着自己分析一下,分析完之后发现JDK17与JDK8还是有些不同的,感觉更高效了。 阅读详情

相关推荐

雷电模拟器+Google Play服务配置全指南:从安装到定位问题解决

本文详细介绍了雷电模拟器配置Google Play服务的全流程,包括版本选择、组件安装、定位服务调试及网络问题排查。针对安卓开发者的常见挑战,提供从基础到高级的解决方案,帮助优化测试环境稳定性与定位精度。

weixin_30399821的博客 822

深入Java多线程JUC底层--AbstractQueuedSynchronizer

文章结构: 1)AbstractQueuedSynchronizer介绍: 2)继承树分析 3)形象说明其原理 4)源码阅读分析 5)思考几个问题 一、AbstractQueuedSynchronizer介绍: AbstractQueuedSynchronizer(AQS)抽象类提供一个实现阻塞锁和依赖于FIFO等待队列的同步器的框架。 AQS被设计用来作为众多同步器的基类,例如Reentrant...

Jack__Frost的博客 1768

ESP32-CAM 赋能 AI 视觉巡检小车:自主避障与目标追踪实现全解析

本文基于 ESP32-CAM 实现了低成本 AI 视觉巡检小车的核心功能,硬件成本<100 元,兼顾自主避障与目标追踪。算法升级:移植 YOLOv5nano 量化模型(INT8),提升目标检测精度(如识别巡检场景中的 “异常物体”“人员”);无线拓展:通过 MQTT 协议将巡检图像、距离数据上传到阿里云 / 腾讯云,实现远程监控;多传感器融合:增加温湿度(DHT11)、红外测温(MLX90614)模块,扩展巡检维度;路径规划:结合 SLAM 算法,实现自主建图与路径巡检。

weixin_42300449的博客 327

(十三)JUC - AQS(AbstractQueuedSynchronizer,抽象队列同步器)

整个ReentrantLock的加锁过程,可以分为三个阶段:1)尝试加锁;2)加锁失败,线程入队列;3)线程入队列后,进入阻塞状态。

LaurenHUA的实验室 305

Java学习笔记(九十九)】之JUC阻塞和非阻塞队列详细介绍

本文章由公号【开发小鸽】发布!欢迎关注!!! 老规矩–妹妹镇楼: 一. JUC队列         单线程编程时我们会使用到一些集合类,如ArrayList等。ArrayList不是线程安全的,Vector通过加snychronized锁的方式实现线程安全,但是效率很低,同时Collections.synchronizedList(List list)方法也能够将ArrayList转换为线程安全的。这些方法都很低效,都是串行执行的。下面介绍阻塞队列与非阻塞

开发小鸽 469

JUC高级十-并发加锁原理之AbstractQueuedSynchronizer(AQS)

如果共享资源被占用,就**需要一定的阻塞等待唤醒机制来==保证锁分配==**。这个机制主要用的是CLH队列的变体实现的,**将暂时获取不到锁的线程加入到队列中**,这个队列就是AQS抽象表现。它将请求共享资源的线程封装成队列的结点(`Node`),通过`CAS`、`自旋`以及`LockSupport.park()`的方式,维护`state变量`的状态,使并发达到同步的效果。

qq_31745863的博客 2397

Java并发编程之AQS原理

AQS原理 1. 概述 全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架 特点: 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁 getState - 获取 state 状态 setState - 设置 state 状态 compareAndSetState - cas 机制设置 state 状态 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源 提供了基于 FI

qq_42964601的博客 256

JUC AQS ReentrantLock源码分析(一)

Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还是存在一些缺陷的:虽然synchronized提供了便捷性的隐式获取锁释放锁机制(基于JVM机制),但是它却缺少了获取锁与释放锁的可操作性,可中断、超时获取锁,且它为独占式在高并发场景下性能大打折扣。...

java_lyvee的专栏 3万+

7. Java JUC源码分析系列笔记-AQS

thead1获取锁成功,执行业务逻辑thread2获取失败,进入CLH队尾,不停自旋检查如果前一个节点的状态是signal,则进入阻塞;否则一直自旋直到获取锁成功thread1释放锁,唤醒下一个节点thead2从阻塞中醒来,获取锁成功,执行业务逻辑。

ThinkerQAQ 2265

JUC并发编程AQS源码解读

JUC个人学习笔记

西岭千秋雪的博客 1205

高并发之JUC——AQS源码深度分析(一)

不要只满足于目前的安逸,傻傻的在工作中写CRUD了。因为在目前疫情面前,企业依然朝不保夕。我们不能温水煮青蛙,只有时刻保持一颗学习的心,拥有硬核能力,时刻心如止水,才能立于不败之地。 这是高并发JUC的系列的第一篇。AbstractQueuedSynchronizer简称AQS,它是JUC工具类的基石JUC包下的类都直接或间接的继承了AbstractQueuedSynchronizer,而JUC是jdk自带的实现高并发的辅助工具。所以我们首先分析AQS源码,才能更好的学习JUC并发包中的其他工具类。 .

qq_20878967的博客 967

Java-JUC笔记-11 AQS核心

AQS核心,及ReentrantLock源码解析

m0_56020664的博客 1551

JUC并发编程基石AQS源码之结构篇

JUC并发编程基石AQS源码之结构篇 前言 AQS(AbstractQueuedSynchronizer)算是JUC包中最重要的一个类了,如果你想了解JUC提供的并发编程工具类的代码逻辑,这个类绝对是你绕不过的。我相信如果你是第一次看AQS源码肯定是一脸懵逼,一个个方法跳来跳去一会就绕蒙了。所以把整个代码骨架搞明白是你看懂AQS源码的第一步。本篇文章只说代码结构,之后的篇章会讲解AQS具体的执行逻...

LuxBai的专栏 403

JUC并发编程】深入浅出Java并发基石——AQS

JUC并发编程】深入浅出Java并发基石——AQS

blblccc 681

Juc24_AQS的概述、体系架构、深入源码解读(非公平)、源码总结

文章目录①. AQS是什么?②. AQS内部体系架构③. ReentrantLock开始解读AQS①. 代码展示②. 从最简单的lock方法开始看看公平和非公平③. lock()④. tryAcquire(arg)⑤. addWaiter(Node.EXCLUSIVE)⑥. acquireQueued(addWaiter(Node.EXCLUSIVE), arg) ⑦. unlock( )获取permit⑧. AQS源码总结 ①. AQS是什么? ①. 是用来构建锁或者其它同步器组件的重量级基础框架及整个

所得皆惊喜 1万+

JUC 基石AQS 原理

JUC 基石AQS 原理 文章目录JUC 基石AQS 原理一、简介,什么是 AQS二、相关基础知识1. synchronized 原理2. CLH 队列3. CAS 原理三、AQS 解析1. AQS 分层架构图2. AQS 核心思想3. AQS 数据结构4. AQS 源码分析(1) 获取锁a. 公平锁获取过程1. FairSync.lock() & acquire() 公平锁开始获取锁2. FairSync.tryAcquire() 公平锁尝试获取锁3. addWaiter() 进入等待队列

Au的博客 499

源码到实战:吃透 AQS 核心原理,解锁 JUC 同步器底层逻辑

AQS(AbstractQueuedSynchronizer)是Java并发编程的核心框架,采用"状态变量+等待队列"的设计,通过模板方法模式实现线程同步。它提供独占式(如ReentrantLock)和共享式(如Semaphore)两种工作模式,内部通过volatile state变量记录资源状态,双向链表管理等待线程。AQS的精华在于将复杂的并发控制封装成简单模板方法,子类只需实现tryAcquire/tryRelease等核心逻辑即可构建同步器。理解AQS不仅有助于掌握JUC工具原理

m0_75223236的博客 962

JUC之 AbstractQueuedSynchronizer之AQS

AbstractQueuedSynchronizer之AQS

qq_49644209的博客 420

JUC-AQS的Condition之await和signal源码解析

JUC-AQS原理篇JUC-AQS源码篇我们知道在AQS源码中用lock和unlock可以用来解决并发中的互斥问题。那么AQS的Condition是干啥的呢?Condition主要是用来解决线程之间的同步问题,类似于实现了线程之前的通信。当某个条件满足时,执行某个线程自己的操作。当条件不满足时,调用await方法将当前线程放入到Condition的条件队列中挂起。当这个条件满足了,其他的线程调用signal方法,将挂起的线程从条件队列中转移到同步队列中,让这个线程唤醒。Condition的await方法、s

zqj1172102457的博客 1377

JUC 体系的基石——AQS

抽象队列同步器;volatile + cas 机制实现的锁模板,保证了代码的同步性和可见性,而 AQS 封装了线程阻塞等待挂起,解锁唤醒其他线程的逻辑。AQS 子类只需要根据状态变量,判断是否可获取锁,是否释放锁,使用 LockSupport 挂起、唤醒线程即可定义:是用来实现锁或者其他同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个 JUC 体系的基石,主要用于解决锁分配的问题官网:为实现阻塞锁和相关的同步器提供一个框架,依赖于先进先出的一个等待;

bad_boy的博客 441

labview 图片缩放 base64编码base64解码

labview 图片缩放 base64编码base64解码

上一篇: 彻底搞懂-ThreadLocal的前世今生
周文王的万年历
博客等级 码龄12年 171粉丝 · 36原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值