[JUC] AQS独占模式详解

深入浅出理解 Java 并发 AQS 的独占锁模式 稍微对并发源码了解的朋友都知道,很多并发工具如 ReentrantLock、CountdownLatch 的实现都是依赖 AQS, 全称 AbstractQueuedSynchronizer。AQS 是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。一般来说,同步工具实现锁的控制分为独占锁和共享锁,而 AQS 提供了对这两种模式的支持。也叫排他锁,即锁只能由一个线程获取,若一个线程获取了锁,则其他想要获取锁的线程只能等待,直到锁被释放。 阅读详情

前言

AQS是指java.util.concurrent.locks.AbstractQueuedSynchronizer类,AQS并没有使用类似synchronized这样特殊的关键字,而是通过维护一个状态变量和一个先进先出(FIFO)的同步队列和来实现锁和同步器功能。在JDK11中AbstractQueuedSynchronizer具有如下实现类:
AQS实现类
可以看到常用的ReentrantLock、CountDownLatch等都使用AQS作为内部实现。

独占模式和共享模式

AQS提供了两种工作模式:独占(exclusive)模式和共享(shared)模式。它的所有子类中,要么实现并使用了它独占功能的 API,要么使用了共享功能的API,而不会同时使用两套 API,即便是它最有名的子类 ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别实现的两套 API 来实现的。

独占模式即当锁被某个线程成功获取时,其他线程无法获取到该锁,共享模式即当锁被某个线程成功获取时,其他线程仍然可能获取到该锁。

本文中我们结合重入锁(ReentrantLock)分析独占模式的实现

AQS类结构

AbstractQueuedSynchronizer类的继承关系如下:
AbstractQueuedSynchronizer类继承图
其中AbstractOwnableSynchronizer定义了如下成员用于保存独占模式下当前持有锁的线程:

    /**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;

AbstractQueuedSynchronizer类定义了如下成员,分别用于保存同步等待队列的头部(head)、尾部(tail)和同步状态(state),同步队列中节点类型是Node:

    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     * 同步队列头结点
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     * 同比队列尾节点
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     * 例如在ReentrantLock中 state >= 1 表示有线程获取了锁,并且可能获取的不止
     * 一次(state > 1, 重复多次获取锁)
     */
    private volatile int state;

Node类定义了如下成员:

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         * 代表节点状态,可以取五个值SIGNAL、CANCELLED、CONDITION、PROPAGATE、0
         * SIGNAL:waitStatus为SIGNAL时表示当前节点的后继节点中的线程将被或已经被挂起,当当前
         *         节点释放锁或被取消时,需要唤醒他的后继节点(unpark后继节点的线程);
         * CANCELLED:waitStatus为CANCELLED表示当前节点的线程由于超时或者中断被cancel;
         * CONDITION:waitStatus为CONDITION代表当前节点处在条件队列中等待某个条件的发生,只有在
         *            使用到Condition节点的状态才可能会是这个值;
         * PROPAGATE:共享模式下waitStatus为PROPAGATE表示会想后继节点传播唤醒线程的操作;
         */
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         * 指向队列中节点的前驱节点
         */
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         * 指向队列中节点的后继节点
         */
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         * 保存竞争锁的线程
         */
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         * 条件队列中指向队列的下一个节点(条件队列是一个单向链表,同步队列是一个双向链表),
         * 同步队列中用该值表示该节点是独占模式(值为EXCLUSIVE)还是共享模式(值为SHARED)
         */
        Node nextWaiter;

ReentrantLock如何使用AQS实现锁

我们以公平重入锁(ReentrantLock)的如下场景来分析AQS是如何工作的,如下图所示,假设有一个重入锁lock,有五个线程t1、t2、t3、t4,红色虚线表示在这个时间线成功获取锁,并修改同步状态
重入锁

时间线1

在时间线1线程t1获取锁并将AQS的state值设为1,调用链为lock() -> acquire(int arg) -> tryAcquire(int acquires), tryAcquire(int acquires)实现如下:

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        @ReservedStackAccess
        protected final boolean tryAcquire(int acquires) {
        	// 传入的acquires值为1
            final Thread current = Thread.currentThread();
            // 此时state为0
            int c = getState();
            if (c == 0) {
            	// 先判断同步队列中是否已经有其他线程在等待锁(此时同步队列还未建立,没有其
            	// 他线程在等待锁),没有的话通过VarHandle将state值设为1,VarHandle类似
            	// 于CAS的原子操作
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    // 将持有锁的线程设为当前线程,设置AbstractOwnableSynchronizer类的
                    // exclusiveOwnerThread成员为current
                    setExclusiveOwnerThread(current);
                    // 返回true成功获取锁
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

在t1第一次调用lock()时,能够成功获取锁,此时同步队列尚未建立,但会将AQS中的同步状态值设为1,表示当前已有线程持有该锁,如下图所示:
状态1

时间线2

在时间线2线程t2区争抢lock锁,但此时线程t1尚未释放lock锁,调用链为lock() -> acquire(int arg) -> tryAcquire(int acquires)

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        @ReservedStackAccess
        protected final boolean tryAcquire(int acquires) {
        	// 传入的acquires值为1
            final Thread current = Thread.currentThread();
            // 此时state为0
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
             // exclusiveOwnerThread为线程t1,current为线程t2,不会进入到该if语句
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 直接返回false表是获取锁失败
            return false;
        }

此时回到acquire(int arg)方法:

    public final void acquire(int arg) {
    	// tryAcquire获取锁失败,返回false,执行acquireQueued
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

我们看addWaiter方法:

    private Node addWaiter(Node mode) {
    	// 传入的mode是Node.EXCLUSIVE表示是独占模式的同步队列节点
        Node node = new Node(mode);
		// 死循环将node添加到队列中
        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
            	// 步骤2、初始化完将node加入到同步队列
            	// 将node的前驱设为tail
                node.setPrevRelaxed(oldTail);
                // 原子操作方式设置node为新的tail
                if (compareAndSetTail(oldTail, node)) {
                	// 将原来tail节点的next指向新的tail
                    oldTail.next = node;
                    return node;
                }
            } else {
            	// 步骤1、head和tail都为null,进入初始化同步队列流程
                initializeSyncQueue();
            }
        }
    }

addWaiter首先执行步骤1,即执行initializeSyncQueue()初始化方法,初始化结束后同步队列如下图所示:
初始状态
接着执行步骤2,将新创建的node节点加入到同步队列,如下图所示
时间线2同步状态
此时同步队列已经创建好新建node节点的thread成员为t2线程,nextWaiter为Node.EXCLUSIVE表示独占模式,头结点(head)的next成员指向新创建的node节点,新创建节点的prev指向头结点,队列tail指向新创建的节点,节点加入到队列后会将头结点(head)的waitStatus修改为Node.SIGNAL。

回到acquireQueued(final Node node, int arg)方法,我们在Node的Doc中知道如果一个节点的waitStatus为Node.SIGNAL,则当节点释放锁或被取消时会去唤醒其后继节点。acquireQueued方法所做的事情就是不断的去判断node的前驱是否为head,若是则尝试获取锁,获取失败再判断是否要挂起该线程(判断过程将节点前驱的waitStatus设为Node.SIGNAL),被挂起后等待被唤醒,若有其他线程唤醒了该线程,则继续循环尝试获取锁,然后挂起。

	// node是上面新创建的节点,arg值为1
    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (;;) {
            	// 找到前驱节点
                final Node p = node.predecessor();
                // 前驱节点为head则可以尝试去获取锁,时间线2时刻t1线程尚未释放锁,state为1,t2线
                // 程获取锁失败
                if (p == head && tryAcquire(arg)) {
                	// 若成功获取锁则将会node设置为新的head,设置为新的head会导致node的
                	// prev成员和thread成员设为null
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                // 判断t2线程是否需要挂起,挂起前需要将前驱的waitStatus设为Node.SIGNAL
                if (shouldParkAfterFailedAcquire(p, node))
                	// 挂起t2线程,因为这是一个死循环,当t2线程从此处被唤醒时会继续执行死循环
                	// 然后再次尝试去获取锁(tryAcquire),获取失败再挂起,一直到获取锁成功为止
                	// 退出死循环
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }
	
	// 判断是否要挂起node节点中的线程,只有当node的前驱pred的waitStatus为Node.SIGNAL
	// 时才会挂起node节点的线程
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // 若前驱节点的waitStatus已经为Node.SIGNAL,则可以直接挂起node节点中的线程
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        // waitStatus大于0表示node前驱节点的线程已经被取消(Cancel),需要从pred处往前寻找
        // 到第一个线程未被取消的节点,将node前驱设为找到的节点
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            // 设置新的前驱
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
             // waitStatus为0(Node创建的初始状态),或者为PROPAGATE(共享模式),这两种情况下都要
             // 将前驱的waitStatus设为Node.SIGNAL,以便在前驱释放锁时可以唤醒node节点的线程
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }

在我们的场景下到时间线2为止线程t2已经被挂起

时间线3

在时间线3线程t1和t3都尝试去获取锁,根据ReentrantLock获取锁的逻辑

       protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 线程t1还占据着锁,state值为1
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 时间线3时刻占有锁的线程还是t1(exclusiveOwnerThread是t1),所以t1再次尝试获取锁
            // 时会进入下面的if分支,而t3尝试获取锁时两个if分支都不会进入,直接返回false获取锁失败
            else if (current == getExclusiveOwnerThread()) {
            	// t1线程将state加上1,表示t1线程再次获取了锁(这里就是重入锁中重入的由来,
            	// 已经占有锁的线程可以重复获取锁)
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                // 给state设置新值
                setState(nextc);
                return true;
            }
            return false;
        }

经过时间线3后线程t1再次获取到锁,而线程t3和时间线2时刻的t2线程一样因为无法获取锁而被加入到同步队列中,经过时间线3后同步队列如下(每加入一个新的节点到队列尾部都会修改其前驱节点的waitStatus值为Node.SIGNAL)
时间线3时刻队列

时间线4

在时间线4线程t1依然未释放锁(state值为2),线程t4尝试获取锁还是失败,t4加入到同步队列,经过时间线4后同步队列状态如下图所示
时间线4时刻状态

时间线5

在时间线5时刻线程t1第一次释放锁,释放锁的代码调用链为unlock() -> release(int arg) -> tryRelease(int releases),tryRelease(int releases)代码如下:

		// ReentrantLock调用一次unlock传入的releases值为1
		@ReservedStackAccess
        protected final boolean tryRelease(int releases) {
        	// 在时间线5时刻state值为2,releases为1
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // c为1
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            // 设置state为1
            setState(c);
            // state不为0,线程t1还未释放锁
            return free;
        }

		public final boolean release(int arg) {
			// 释放锁失败,tryRelease返回false,线程t1不会去唤醒同步队列中等待线程
            if (tryRelease(arg)) {
                 Node h = head;
                 if (h != null && h.waitStatus != 0)
                     unparkSuccessor(h);
                 return true;
            }
            return false;
        }

时间线6

在时间线6时刻线程t1再次释放锁,同样释放锁的调用链为unlock() -> release(int arg) -> tryRelease(int releases),此时会进入unparkSuccessor方法唤醒同步队列中的等待线程,即唤醒线程t2(这里只考虑使用ReentrantLock的公平锁)

		// ReentrantLock调用一次unlock传入的releases值为1
		@ReservedStackAccess
        protected final boolean tryRelease(int releases) {
        	// 在时间线6时刻state值为1,releases为1
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // c为0
            if (c == 0) {
                free = true;
                // 设置占有锁的线程为null
                setExclusiveOwnerThread(null);
            }
            // 设置state为0
            setState(c);
            // state为0,线程t1完全释放锁
            return free;
        }

		public final boolean release(int arg) {
			// 释放锁成功,tryRelease返回true,线程t1调用unparkSuccessor唤醒同
			// 步队列中等待线程
            if (tryRelease(arg)) {
                 Node h = head;
                 if (h != null && h.waitStatus != 0)
                     unparkSuccessor(h);
                 return true;
            }
            return false;
        }

进入到unparkSuccessor方法唤醒线程t2

	// 此时传入的node为头结点head
	private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
        	// 这里要将node的waitStatus设为0我猜测是为了防止重复唤醒node的后继节点
        	// (参照release方法里进入unparkSuccessor的条件h.waitStatus != 0)
            node.compareAndSetWaitStatus(ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        // 寻找第一个没有被cancel的后继节点,从后往前找(为什么要从后往前找暂时不清楚,
        // next可能为null??)
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null)
        	// 唤醒后继第一个未被cancel的线程,我们这里就是唤醒t2线程
            LockSupport.unpark(s.thread);
    }

t2线程在获取锁时的调用链为acquire(int arg) -> acquireQueued(final Node node, int arg) -> parkAndCheckInterrupt(),在方法parkAndCheckInterrupt出被挂起,现在被t1线程在parkAndCheckInterrupt处唤醒后回到acquireQueued方法出继续执行,先执行如下所示的步骤1,在下一个循环中执行步骤2,如下所示

	final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (;;) {
                final Node p = node.predecessor();
                // 步骤2 下一次循环再次尝试去获取锁,由于此时state为0,t2线程能成功获取锁并
                // 将state设为1(这里我们考虑的是公平锁即FIFO,后面加入到同步队列的t3、t4线
                // 程不会和t2线程争抢锁)
                if (p == head && tryAcquire(arg)) {
                	// 将t2线程所在的节点设置为新的head节点
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node))
                	// 步骤1 线程t2在此时被唤醒,继续执行下一个循环
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }

至此t2线程成功获取到锁,其所在的节点也成为同步队列新的头结点(head),如下图所示为时间线6时刻的状态:
时间线6时刻状态

时间线7

在时间线7线程t3同样的从同步队列被唤醒(从parkAndCheckInterrupt方法返回重新获取锁),时间线7的状态图如下
时间线7时刻状态

时间线8

时间线8时刻线程t3释放锁,线程t4获取锁,状态如下:
时间线8时刻状态
至此我们分析完了ReentrantLock整个加锁和释放锁流程

小结

我们对RentrantLock的加锁个释放锁的过程做个小结。

竞争锁过程:

  1. 获取AQS的同步状态state,若state为0,则当前没有线程占有该锁,获取锁成功,否则进入2;
  2. 若state不为0,说明锁已经被某个线程占用,继续判断当前尝试获取锁的线程和当前占有锁的线程是否是同一线程,是的话获取锁成功,将state值加1;
  3. 若步骤1和2都没竞争到锁,则获取锁失败,用当前线程构造同步队列节点加入到同步队列中等待被唤醒;

释放锁过程:

  1. 将AQS同步状态state减1,若减完后state为0,则释放锁成功,进入步骤2,否则释放锁失败(当前线程多次获取了锁);
  2. 释放锁成功,唤醒同步队列中头结点后的第一个节点的线程;

参考文献

深度解析 Java 8:JDK1.8 AbstractQueuedSynchronizer 的实现分析(上)
深度解析 Java 8:AbstractQueuedSynchronizer 的实现分析(下)

深入AQS:Java并发核心原理与自定义同步器实战 在Java并发编程中,锁和同步器是构建线程安全程序的基础组件。其核心原理在于通过原子操作和队列管理,协调多线程对共享资源的访问,以实现互斥与同步。AQS(AbstractQueuedSynchronizer)作为JUC(Java Util Concurrent)库的基石,采用模板方法模式,将复杂的线程排队、阻塞与唤醒机制封装成通用框架,其价值在于为开发者提供了构建高性能、可扩展同步器(如ReentrantLock、Semaphore)的底层支撑。在高并发场景下,理解AQS的state状态管理、CLH队列变体 阅读详情

相关推荐

避坑指南:OpenWebUI+SearXNG联网搜索的5个常见配置错误及解决方法

本文详细解析了OpenWebUI与SearXNG整合过程中常见的5个配置错误及解决方案,包括JSON格式配置陷阱、国内搜索引擎缺失、连续搜索失效等问题。通过实战验证的优化策略,帮助开发者避免性能下降和功能异常,提升大模型与搜索引擎的协同效率。

jenkins8butler的博客 819

AQS之独占模式和共享模式

AQS之独占模式和共享模式 由于ReentrantLock是一个独占锁,独占锁的知识可以参考AQS之理论知识(一)和AQS之公平锁和非公平锁(二) 两篇文章,本文重点讲解共享模式。并且本文共享模式的讲解以CountDownLatch为主。 一、概念 ​ AQS提供了两种工作模式:独占(exclusive)模式和共享(shared)模式。它的所有子类中,要么实现并使用了它独占功能的 API,要么使用了共享功能的API,而不会同时使用两套 API,即便是它最有名的子类 ReentrantReadWriteLoc

weixin_43823391的博客 6510

PINN——加入物理约束的神经网络

【摘要】 基于物理信息的神经网络(Physics-informed Neural Network, 简称PINN),是一类用于解决有监督学习任务的神经网络,它不仅能够像传统神经网络一样学习到训练数据样本的分布规律,而且能够学习到数学方程描述的物理定律。与纯数据驱动的神经网络学习相比,PINN在训练过程中施加了物理信息约束,因而能用更少的数据样本学习到更具泛化能力的模型。本文主要解析这种神经网络以...

qq_39444290的博客 4584

Aqs独占/共享模式

表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。如果线程T对数据A加上排他锁后,则其他线程不能再对A加任何类型的锁。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。:共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。

master-dragon的专栏 1278

ZYNQ开发(2)在Linux系统层上PL-PS通过AXI-DMA传输数据详细步骤及注意事项

参考的网络资料 https://github.com/bperez77/xilinx_axidma/tree/master https://gitlab.gnome.org/HosseinBeheshti/UlteraZed7EV/-/tree/master/petalinux/xilinx_axidma https://www.icode9.com/content-3-925319.html https://blog.youkuaiyun.com/weixin_38218267/article/details/1

aizaiyueye的专栏 3448

Geant4学习——入门(基本概念、Geant4工具包的结构、强制类、可选类)

Geant4是由欧洲核子研究组织基于C++面向对象技术开发的蒙特卡罗应用软件包,用于模拟粒子在物质中输运的物理过程。由于具有良好的通用性和扩展能力,Geant4在涉及微观粒子与物质相互作用的诸多领域获得了广泛应用。 第一部分 Geant4的基本概念 HEP中的模拟是“虚拟现实”。仿真既可用于在研发阶段帮助设计探测器,又可用于物理研究中了解探测器的响应。为了创建这样的虚拟现实,我们需要对粒子间的相互作用,几何形状和材料进行建模,以便将基本粒子传播到检测器中。我们还需要描述检测器生成原始数据的灵敏度。 Gea

M_M-铭的博客 2万+

JDK8 AbstractQueuedSynchronizer(AQS) 源码 之 独占模式与共享模式

目录一、前言二、带着问题看源码三、独占模式下的public方法3.1 简略流程图3.1.1 acquire方法3.1.2 acquireInterruptibly方法3.1.3 tryAcquireNanos方法3.1.4 release方法3.2 方法列表四、源码分析 一、前言   AQS逻辑分为三部分:独占模式(锁标识为EXCLUSIVE)、共享模式(锁标识为SHARED)、条件的逻辑(也就是内部类ConditionObject里的逻辑)。   其中,条件逻辑依附

dreambyday 1214

基于STM32L4XX 、HAL库的DRV8311三相无刷直流电机(BLDC)驱动应用C语言程序设计

是德州仪器(TI)推出的一款针对三相无刷直流(BLDC)电机或永磁同步电机(PMSM)的集成驱动器芯片。:集成了三个半桥MOSFET、门极驱动电路、电荷泵(支持100%占空比)、稳压器以及多种保护电路。:工作电压最高可达,持续输出电流可达,峰值电流可达。:通过引脚直接控制每个半桥的PWM和使能。这种方式需要MCU输出6路信号,控制更灵活。:通过SPI总线配置内部寄存器,仅需PWMA/B/C三路PWM信号即可控制电机。可以读取故障状态、配置参数(如死区时间、电流保护阈值等),是更常用和推荐的方式。

PP_Cottage的博客 553

AQS详解

AQS 的全称为,翻译过来的意思就是抽象队列同步器。这个类在 java.util.concurrent.locks 包下面。AQS为Java的并发包提供了强大的同步支持。通过内置的FIFO队列来完成资源获取线程的排队工作,并且利用一个被volatile关键字修饰的int类型的变量state表示同步状态。AQS 为构建锁和同步器提供了一些通用功能的实现,许多同步类实现都依赖于它,如常用的ReentrantLock、Semaphore等。

汐泠的博客 5727

优秀的 Verilog/FPGA开源项目介绍(二十七)- 小型CPU

介绍在FPGA上实现一个CPU是一个令人兴奋的事情,但是太复杂的CPU实现起来太难,而且不适合去了解计算机的架构,所以今天介绍一些小型的CPU,比如8051、MIPS、MSP430等~关于RISC-V的项目,请查看《优秀的 Verilog/FPGA开源项目介绍(二)-RISC-V》ZipCPUhttp://zipcpu.com/about/zipcpu.html项目介绍Z...

OpenFPGA的博客 2576

使用MATLAB和Simulink来设计并仿真一个智能家居多传感器数据融合系统

智能家居多传感器数据融合系统旨在通过集成来自多种不同类型的传感器的数据,以提供更精确和可靠的环境信息或用户状态监测。例如,将温度、湿度、光照强度、运动检测等多种传感器的数据结合起来,可以更好地了解家庭内部的状态,并做出相应的调整或响应。在这个教程中,我们将使用MATLAB和Simulink来设计并仿真一个智能家居多传感器数据融合系统。

amy_mhd的博客 189

【JUC】AQS底层深度拆解|独占/共享模式|队列原理全详解

本文深入解析AQS(AbstractQueuedSynchronizer)的核心机制与设计思想。作为JUC锁体系的基石,AQS通过volatile state状态变量和FIFO双向阻塞队列实现线程同步,支持独占和共享两种模式。独占模式保证资源单线程持有(如ReentrantLock),共享模式允许多线程并发访问(如Semaphore)。线程竞争失败后会封装为Node入队阻塞,释放时按队列顺序唤醒。双向链表设计支持快速节点取消、前驱校验和队列稳定操作,是AQS高效可靠的关键。理解AQS原理对掌握Java并发编

2401_89675417的博客 833

手把手教你完成YOLOv10 PySide6目标检测界面搭建,使用Qt6设计YOLOv10检测系统,前台系统+后台管理系统开发实战,可用于大论文凑工作量或毕设必备,全网最详细教程

手把手教你完成YOLOv10 PySide6目标检测界面搭建,使用Qt6设计YOLOv10检测系统,前台系统+后台管理系统开发实战,可用于大论文凑工作量或毕设必备,全网最详细教程

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 1733

轻松的通过ReentrantLock掌握AQS的核心原理

从一次"线程池卡死导致应用假死"的线上故障出发,通过ReentrantLock的使用案例反推AQS的核心原理。深度剖析AQS的state变量、CLH队列、独占模式与共享模式,配合手写100行代码实现简易版ReentrantLock,揭秘为什么ReentrantLock比synchronized更灵活、公平锁与非公平锁的性能差异、以及Condition条件队列的实现原理。图解入队出队流程,给出并发工具类的最佳实践。

比较是偷走幸福的小偷 4165

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

深入理解 AQS:从架构到实现,解锁 Java 并发编程的核心密钥

本文深度解析Java并发核心基石——AbstractQueuedSynchronizer(AQS):详解其“volatile state + CLH虚拟队列”架构、独占/共享模式实现原理,并结合ReentrantLock、CountDownLatch、Semaphore源码,助你彻底掌握并发编程底层密钥。

果酱 の 博客 265

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

AQS深度解析

AQS 是一个抽象类,无法被实例化。它的设计采用了模板方法模式,将获取/释放锁的复杂逻辑(如同步队列的维护、线程的阻塞与唤醒)封装在内部,而将与“具体同步状态”判断相关的逻辑(如tryAcquiretryRelease)抽象出来,留给子类去实现。核心思想:如果共享资源空闲,则请求线程直接获取锁;否则,将请求线程封装成一个Node节点,加入一个 FIFO 的同步队列中排队等待。// 1. 自定义一个互斥锁,实现 Lock 接口// 2. 内部类继承 AQS// 3. 实现 tryAcquire(独占)

weixin_52056853的博客 222

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

02-02-A-AQS与JUC锁体系详解

AQS是JUC锁体系的公共骨架,核心为volatile int state(资源状态)与CLH变体等待队列。通过模板方法模式,子类只需实现tryAcquire等方法定义“抢锁逻辑”,AQS统一处理排队、挂起、唤醒等并发细节。ReentrantLock、Semaphore、CountDownLatch等均基于此:独占模式(如锁)与共享模式(如信号量)分工明确,支持可重入、公平性、条件变量及锁降级。底层依赖CAS+volatile保证原子性与可见性,配合LockSupport实现灵活挂起/唤醒,极大简化并发编程

hhkrye的博客 372

AQS介绍

abstractQueueSync 作为juc 并发包下的核心,reentrantlock,smphere,countdownlatch都是它的实现。cycleBare 用的是 reentrantlock +condition。smphere,countdownlatch 用的是共享锁。reentrantlock 用的是排他锁。cycleBare也用到了它。

狼与自由 45

从源码到实战:彻底吃透AQS同步器原理与JUC应用

并发编程中,锁与同步器是绕不开的核心难点。AQS(AbstractQueuedSynchronizer)作为Java并发包(JUC)的基石,通过一个volatile的state变量和CLH变体队列,将线程的排队、阻塞、唤醒抽象成通用模板,而将资源获取的判断交给子类实现。这种模板方法模式使得ReentrantLock、Semaphore、CountDownLatch等同步器都只是AQS在不同场景下的具体应用。理解AQS的state管理、CAS操作、LockSupport阻塞机制以及独占与共享模式的差异,就能彻

weixin_28700411的博客 330

《Java 100 天进阶之路》第54篇:AQS抽象队列同步器(2026版)

摘要:多线程专题第四弹!从 JUC 并发体系的基石出发,彻底讲透 AQS 的设计哲学——用 volatile int state 表示同步状态,用 CLH 双向 FIFO 队列管理等待线程,用模板方法模式让子类只需实现 tryAcquire/tryRelease 等少量方法。深度解析独占模式(ReentrantLock 加锁/解锁/可重入原理)与共享模式(Semaphore 限流、CountDownLatch 等待)的完整流程。附 JUC 全景图、生产避坑清单、面试高频考点与追问陷阱。

折哥|智能物流与Java实战,副高数据库工程师。深耕出版社物流WMS/OMS(线体/立库/VGA),也分享Java进阶与OMS电商对接。Oracle→PG实战派。相信沉淀可迁移能力,分享通用技术,不藏不虚。 515

深入解析Java并发编程核心AQS原理与应用

AQS(AbstractQueuedSynchronizer)是Java并发编程的核心框架,通过FIFO队列和CAS操作实现高效的线程同步。作为JUC(Java并发工具包)的基石,AQS支撑了ReentrantLock、Semaphore等常用同步器的实现。其核心原理包括状态管理(volatile state变量)、等待队列(CLH队列)以及独占/共享两种资源获取模式。在高并发场景下,AQS的无锁设计能显著提升性能,广泛应用于连接池、限流器等需要精确控制线程同步的场合。理解AQS工作机制有助于开发者编写更高

weixin_33552698的博客 188

深入理解Java并发基石AQS:状态位与等待队列的设计之道

并发编程中,锁与同步器的底层机制一直是开发者探索的重点。从互斥锁到信号量,从倒计时器到读写分离,众多并发工具背后共享着同一套核心骨架——AbstractQueuedSynchronizer(AQS)。它通过一个volatile状态位和CLH等待队列的协同,将资源获取、排队阻塞、唤醒竞争等复杂流程固化为一套模板方法。基于模板方法模式,AQS把“是否允许获取资源”与“获取不到如何排队”解耦,向上衍生出ReentrantLock、Semaphore、CountDownLatch等经典同步器。理解AQS的状态语义、

weixin_30231327的博客 242

JUC并发编程核心:从AQS原理到线程池实战

Java并发编程中,synchronized提供基础互斥,但难以应对读多写少、可中断等待和多方法间锁传递等场景。JUC(java.util.concurrent)工具包应运而生,其核心是AQS抽象队列同步器,通过状态位与等待队列构建了ReentrantLock、Semaphore、CountDownLatch等同步工具的骨架。基于AQS,显式锁支持超时与公平策略,ConcurrentHashMap采用桶级锁提升并发读写性能,而BlockingQueue为生产者-消费者模式提供优雅解耦。线程池ThreadPo

weixin_30846347的博客 298

JUC之AQS

AQS(AbstractQueuedSynchronizer)是Java并发包的核心框架,用于构建锁和同步器。其核心设计包括:1)volatile int状态变量管理同步状态;2)CLH变体队列管理等待线程;3)模板方法模式,子类实现关键方法如tryAcquire。AQS支持独占(如ReentrantLock)和共享(如Semaphore)模式,通过state变量和FIFO队列实现高效线程同步。

编程技术圈 1337

AQS之独占模式

AQS独占模式下的源码分析

qq_32428485的博客 757
上一篇: [JUC] LockSupport浅析
下一篇: [JUC] AQS共享模式详解
zkp_java
博客等级 码龄15年 46粉丝 56原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值