当前位置: 首页 > news >正文

第十二章 AbstractQueuedSynchronizer 之 AQS

12.1 前置知识

公平锁和非公平锁

  • 公平锁:锁被释放以后,先申请的线程先得到锁。性能较差一些,因为公平锁为了保证时间上的绝对顺序,上下文切换更频繁

  • 非公平锁:锁被释放以后,后申请的线程可能会先获取到锁,是随机或者按照其他优先级排序的。性能更好,但可能会导致某些线程永远无法获取到锁

可重入锁

  • 也叫做递归锁,指的是线程可以再次获取自己的内部锁,比如一个线程获取到了对象锁,此时这个对象锁还没有释放,当其想再次获取这个对象锁的时候还是可以获取的,如果不可重入的话,会导致死锁

自旋思想

  • 当线程请求锁时,如果锁已经被其他线程持有,那么该线程会不断地重试获取锁,而不是被挂起等待,这种不断尝试获取锁的行为称为自旋

LockSupport

  • 一个工具类,用于线程的阻塞和唤醒操作,类似于wait()notify()方法,但是更加灵活和可控

  • 提供了park()unpark()两个静态方法用于线程阻塞和唤醒操作

  • 优点在于可以在任意时刻阻塞和唤醒线程而不需要事先获取锁或监视器对象

数据结构之双向链表

  • 双向链表(Doubly Linked List)是一种常见的数据结构,它是由一系列结点(Node)组成的,每个结点包含三个部分:数据域、前驱指针和后继指针。其中,数据域存储结点的数据,前驱指针指向前一个结点,后继指针指向后一个结点。通过这种方式,双向链表可以实现双向遍历和插入、删除操作

设计模式之模板设计模式

  • 模板设计模式是一种行为型设计模式,定义了一种算法的框架,并将某些步骤延迟到子类中实现,这种设计模式的主要目的是允许子类在不改变算法结构的情况下重新定义算法中的某些步骤

  • 优点是能够提高代码复用性和可维护性

12.2 AQS 入门级别理论知识

12.2.1 是什么?

抽象的队列同步器

image

技术解析

  • 是用来实现锁或者其他同步器组件的公共基础部分的抽象实现

  • 是重量级基础框架及整个 JUC 体系的基石,只要用于解决锁分配给“谁”的问题

  • 整体就是一个抽象的 FIFO 队列来完成资源获取线程的排队工作,并通过一个 int 类变量表示持有锁的状态

image

CLH:Craig、Landin and Hagersten 队列,是一个单向链表,AQS 中的队列是 CLH 变体的虚拟双向队列 FIFO

12.2.2 AQS 为什么是 JUC 内容中最重要的基石

和 AQS 有关的

image

ReentrantLock

public class ReentrantLock implements Lock, java.io.Serializable {private static final long serialVersionUID = 7373984872572414699L;/** Synchronizer providing all implementation mechanics */private final Sync sync;/*** Base of synchronization control for this lock. Subclassed* into fair and nonfair versions below. Uses AQS state to* represent the number of holds on the lock.*/abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;/*** Performs {@link Lock#lock}. The main reason for subclassing* is to allow fast path for nonfair version.*/abstract void lock();...}...}

CountDownLatch

public class CountDownLatch {/*** Synchronization control For CountDownLatch.* Uses AQS state to represent count.*/private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {setState(count);}int getCount() {return getState();}...}private final Sync sync;...}

ReentrantReadWriteLock

public class ReentrantReadWriteLock implements ReadWriteLock, Serializable {private static final long serialVersionUID = -6992448646407690164L;/** Inner class providing readlock */private final ReentrantReadWriteLock.ReadLock readerLock;/** Inner class providing writelock */private final ReentrantReadWriteLock.WriteLock writerLock;/** Performs all synchronization mechanics */final Sync sync;...abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 6317671515068378041L;static final int SHARED_SHIFT = 16;static final int SHARED_UNIT = 65536;static final int MAX_COUNT = 65535;static final int EXCLUSIVE_MASK = 65535;private transient ThreadLocalHoldCounter readHolds = new ThreadLocalHoldCounter();private transient HoldCounter cachedHoldCounter;private transient Thread firstReader = null;private transient int firstReaderHoldCount;...}...}

Semaphore

public class Semaphore implements java.io.Serializable {private static final long serialVersionUID = -3222578661600680210L;/** All mechanics via AbstractQueuedSynchronizer subclass */private final Sync sync;/*** Synchronization implementation for semaphore.  Uses AQS state* to represent permits. Subclassed into fair and nonfair* versions.*/abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 1192457210091910933L;Sync(int permits) {setState(permits);}final int getPermits() {return getState();}...}...}

进一步理解锁和同步器的关系

  • 锁,面向锁的使用者

    定义了程序员和锁交互的使用层 API,隐藏了实现细节,你调用即可

  • 同步器,面向锁的实现者

    Java 并发大神 DougLee,提出了统一规范并简化了锁的实现,将其抽象出来,屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等,是一切锁和同步组件实现的------公共基础部分

12.2.3 能干嘛

加锁会导致阻塞

有阻塞就需要排队,实现排队必然需要队列

解释说明

抢到资源的线程直接使用处理业务,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似于银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能切获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)

既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

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

image

源码

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {private static final long serialVersionUID = 7373984972572414691L;protected AbstractQueuedSynchronizer() { }/*** Wait queue node class.** <p>The wait queue is a variant of a "CLH" (Craig, Landin, and* Hagersten) lock queue. CLH locks are normally used for* spinlocks.  We instead use them for blocking synchronizers, but* use the same basic tactic of holding some of the control* information about a thread in the predecessor of its node.  A* "status" field in each node keeps track of whether a thread* should block.  A node is signalled when its predecessor* releases.  Each node of the queue otherwise serves as a* specific-notification-style monitor holding a single waiting* thread. The status field does NOT control whether threads are* granted locks etc though.  A thread may try to acquire if it is* first in the queue. But being first does not guarantee success;* it only gives the right to contend.  So the currently released* contender thread may need to rewait.** <p>To enqueue into a CLH lock, you atomically splice it in as new* tail. To dequeue, you just set the head field.* <pre>*      +------+  prev +-----+       +-----+* head |      | <---- |     | <---- |     |  tail*      +------+       +-----+       +-----+* </pre>** <p>Insertion into a CLH queue requires only a single atomic* operation on "tail", so there is a simple atomic point of* demarcation from unqueued to queued. Similarly, dequeuing* involves only updating the "head". However, it takes a bit* more work for nodes to determine who their successors are,* in part to deal with possible cancellation due to timeouts* and interrupts.** <p>The "prev" links (not used in original CLH locks), are mainly* needed to handle cancellation. If a node is cancelled, its* successor is (normally) relinked to a non-cancelled* predecessor. For explanation of similar mechanics in the case* of spin locks, see the papers by Scott and Scherer at* http://www.cs.rochester.edu/u/scott/synchronization/** <p>We also use "next" links to implement blocking mechanics.* The thread id for each node is kept in its own node, so a* predecessor signals the next node to wake up by traversing* next link to determine which thread it is.  Determination of* successor must avoid races with newly queued nodes to set* the "next" fields of their predecessors.  This is solved* when necessary by checking backwards from the atomically* updated "tail" when a node's successor appears to be null.* (Or, said differently, the next-links are an optimization* so that we don't usually need a backward scan.)** <p>Cancellation introduces some conservatism to the basic* algorithms.  Since we must poll for cancellation of other* nodes, we can miss noticing whether a cancelled node is* ahead or behind us. This is dealt with by always unparking* successors upon cancellation, allowing them to stabilize on* a new predecessor, unless we can identify an uncancelled* predecessor who will carry this responsibility.** <p>CLH queues need a dummy header node to get started. But* we don't create them on construction, because it would be wasted* effort if there is never contention. Instead, the node* is constructed and head and tail pointers are set upon first* contention.** <p>Threads waiting on Conditions use the same nodes, but* use an additional link. Conditions only need to link nodes* in simple (non-concurrent) linked queues because they are* only accessed when exclusively held.  Upon await, a node is* inserted into a condition queue.  Upon signal, the node is* transferred to the main queue.  A special value of status* field is used to mark which queue a node is on.** <p>Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill* Scherer and Michael Scott, along with members of JSR-166* expert group, for helpful ideas, discussions, and critiques* on the design of this class.*/static final class 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.*/private volatile int state;/*** Returns the current value of synchronization state.* This operation has memory semantics of a {@code volatile} read.* @return current state value*/protected final int getState() {return state;}/*** Sets the value of synchronization state.* This operation has memory semantics of a {@code volatile} write.* @param newState the new state value*/protected final void setState(int newState) {state = newState;}...}

AQS 使用一个 volatile 的 int 类型的成员变量来表示同步状态,通过内置的 FIFO 队列来完成资源获取的排队工作将每条要去抢占线程封装成一个 Node 节点来实现锁的分配,通过 CAS 完成对 State 值的修改

12.2.4 小总结

AQS 同步队列的基本结构

image

12.3 AQS 源码分析前置知识储备

12.3.1 AQS 内部体系架构图

image

12.3.2 AQS 自身

AQS 的 int 变量

AQS 的同步状态 state 成员变量

/*** The synchronization state.*/
private volatile int state;

类似与银行办理业务的受理窗口状态

  • 零就是没人,自由状态可以办理

  • 大于等于 1,有人占用窗口,等着去

AQS 的 CLH 队列

CLH 队列(三个大牛的名字组成),为一个双向队列

image

类似银行候客区的等待顾客

小总结

  • 有阻塞就需要排队,实现排队必然需要队列

  • state 变量 + CLH 双端队列

12.3.3 内部类 Node(Node 类在 AQS 类内部)

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...static final class Node {/** Marker to indicate a node is waiting in shared mode */static final Node SHARED = new Node();/** Marker to indicate a node is waiting in exclusive mode */static final Node EXCLUSIVE = null;/** waitStatus value to indicate thread has cancelled */static final int CANCELLED =  1;/** waitStatus value to indicate successor's thread needs unparking */static final int SIGNAL    = -1;/** waitStatus value to indicate thread is waiting on condition */static final int CONDITION = -2;/*** waitStatus value to indicate the next acquireShared should* unconditionally propagate*/static final int PROPAGATE = -3;/*** 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).*/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.*/Node nextWaiter;/*** Returns true if node is waiting in shared mode.*/final boolean isShared() {return nextWaiter == SHARED;}/*** Returns previous node, or throws NullPointerException if null.* Use when predecessor cannot be null.  The null check could* be elided, but is present to help the VM.** @return the predecessor of this node*/final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() {    // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}}...}

Node 的 int 变量

Node 的等待状态 waitState 成员变量

volatile int waitStatus;

说人话

  • 等候区其他顾客(其他线程)的等待状态

  • 队列中每个排队的个体就是一个 Node

Node 此类的讲解

内部结构

static final class Node {//共享static final Node SHARED = new Node();//独占static final Node EXCLUSIVE = null;//线程被取消了static final int CANCELLED =  1;//后续线程需要唤醒static final int SIGNAL    = -1;//等待 condition 唤醒static final int CONDITION = -2;//共享式同步状态获取将会无条件地传播下去static final int PROPAGATE = -3;//初始值为 0,状态是上面的几种volatile int waitStatus;//前置节点volatile Node prev;//后置节点volatile Node next;volatile Thread thread;Node nextWaiter;/*** Returns true if node is waiting in shared mode.*/final boolean isShared() {return nextWaiter == SHARED;}final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() {    // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}

属性说明

image

12.4 AQS 源码深度讲解和分析

Lock 接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的

12.4.1 ReentrantLock

从我们的 ReentrantLock 开始解读 AQS 的源码

image

12.4.2 从最简单的 lock 方法开始看看公平和非公平

public class ReentrantLock implements Lock, java.io.Serializable {private static final long serialVersionUID = 7373984872572414699L;/** Synchronizer providing all implementation mechanics */private final Sync sync;abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;/*** Performs {@link Lock#lock}. The main reason for subclassing* is to allow fast path for nonfair version.*/abstract void lock();/*** Performs non-fair tryLock.  tryAcquire is implemented in* subclasses, but both need nonfair try for trylock method.*/final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {//非公平锁相比较与公平锁少了 !hasQueuedPredecessors()if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}protected final boolean isHeldExclusively() {// While we must in general read state before owner,// we don't need to do so to check if current thread is ownerreturn getExclusiveOwnerThread() == Thread.currentThread();}final ConditionObject newCondition() {return new ConditionObject();}// Methods relayed from outer classfinal Thread getOwner() {return getState() == 0 ? null : getExclusiveOwnerThread();}final int getHoldCount() {return isHeldExclusively() ? getState() : 0;}final boolean isLocked() {return getState() != 0;}/*** Reconstitutes the instance from a stream (that is, deserializes it).*/private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();setState(0); // reset to unlocked state}}.../*** Sync object for non-fair locks*/static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*///非公平锁的 lockfinal void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}/*** Sync object for fair locks*/static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;//公平锁的 lockfinal void lock() {acquire(1);}/*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {//公平锁相比较与非公平锁多了 !hasQueuedPredecessors()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;}return false;}}/*** Creates an instance of {@code ReentrantLock}.* This is equivalent to using {@code ReentrantLock(false)}.*/public ReentrantLock() {sync = new NonfairSync();}/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}.../*** Acquires the lock.** <p>Acquires the lock if it is not held by another thread and returns* immediately, setting the lock hold count to one.** <p>If the current thread already holds the lock then the hold* count is incremented by one and the method returns immediately.** <p>If the lock is held by another thread then the* current thread becomes disabled for thread scheduling* purposes and lies dormant until the lock has been acquired,* at which time the lock hold count is set to one.*/public void lock() {sync.lock();}...}

可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()

hasQueuedPredecessors 是公平锁加锁时判断等待队列中是否存在有效节点的方法

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {.../*** Queries whether any threads have been waiting to acquire longer* than the current thread.** <p>An invocation of this method is equivalent to (but may be* more efficient than):*  <pre> {@code* getFirstQueuedThread() != Thread.currentThread() &&* hasQueuedThreads()}</pre>** <p>Note that because cancellations due to interrupts and* timeouts may occur at any time, a {@code true} return does not* guarantee that some other thread will acquire before the current* thread.  Likewise, it is possible for another thread to win a* race to enqueue after this method has returned {@code false},* due to the queue being empty.** <p>This method is designed to be used by a fair synchronizer to* avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>.* Such a synchronizer's {@link #tryAcquire} method should return* {@code false}, and its {@link #tryAcquireShared} method should* return a negative value, if this method returns {@code true}* (unless this is a reentrant acquire).  For example, the {@code* tryAcquire} method for a fair, reentrant, exclusive mode* synchronizer might look like this:**  <pre> {@code* protected boolean tryAcquire(int arg) {*   if (isHeldExclusively()) {*     // A reentrant acquire; increment hold count*     return true;*   } else if (hasQueuedPredecessors()) {*     return false;*   } else {*     // try to acquire normally*   }* }}</pre>** @return {@code true} if there is a queued thread preceding the*         current thread, and {@code false} if the current thread*         is at the head of the queue or the queue is empty* @since 1.7*/public final boolean hasQueuedPredecessors() {// The correctness of this depends on head being initialized// before tail and on head.next being accurate if the current// thread is first in queue.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}...}

12.4.3 以非公平锁 ReentrantLock 为例作为突破走起,方法 lock()

对比公平锁和非公平锁的tryAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁种少了一个判断!hasQueuedPredecessors()

!hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:

  • 公平锁:公平锁讲究先来后到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列
  • 非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程苏醒后,不一定就是排头的这个线程获得锁,它还是需要参加竞争锁(存在线程竞争的情况下),后来的线程可能不讲武德插队夺锁了

image

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}...}

lock()方法

public class ReentrantLock implements Lock, java.io.Serializable {/** Synchronizer providing all implementation mechanics */private final Sync sync;...	/*** Acquires the lock.** <p>Acquires the lock if it is not held by another thread and returns* immediately, setting the lock hold count to one.** <p>If the current thread already holds the lock then the hold* count is incremented by one and the method returns immediately.** <p>If the lock is held by another thread then the* current thread becomes disabled for thread scheduling* purposes and lies dormant until the lock has been acquired,* at which time the lock hold count is set to one.*/public void lock() {sync.lock();}.../*** Base of synchronization control for this lock. Subclassed* into fair and nonfair versions below. Uses AQS state to* represent the number of holds on the lock.*/abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;/*** Performs {@link Lock#lock}. The main reason for subclassing* is to allow fast path for nonfair version.*/abstract void lock();}...}	

lock()采用模板设计模式,其实现方式在子类 ReentrantLock 中

NonfairSync

public class ReentrantLock implements Lock, java.io.Serializable {...static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {//第一个线程抢占if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());//第二个线程及后续线程抢占elseacquire(1);}...}  ...}

FairSync

public class ReentrantLock implements Lock, java.io.Serializable {...static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}...}...}

公平和非公平方法最后都会调用acquire() 方法

acquire() 方法

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}...}

acquire() 方法具体调用了里面三个方法,tryAcquire()addWaiter()acquireQueued()

tryAcquire()方法

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {...protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}...}

tryAcquire()采用模板设计模式,其实现方式在子类 ReentrantLock 中

NonfairSync

public class ReentrantLock implements Lock, java.io.Serializable {...static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}...}...abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;.../*** Performs non-fair tryLock.  tryAcquire is implemented in* subclasses, but both need nonfair try for trylock method.*/final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}...}...}

FairSync

public class ReentrantLock implements Lock, java.io.Serializable {...	static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;.../*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();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;}return false;}...}...}

addWaiter()方法

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {.../*** Creates and enqueues node for current thread and given mode.** @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared* @return the new node*/private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}.../*** Inserts node into queue, initializing if necessary. See picture above.* @param node the node to insert* @return node's predecessor*/private Node enq(final Node node) {for (;;) {Node t = tail;//哨兵节点,虚拟节点,占位 Thread = null,waitStatus = 0if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}...}

在双向链表中,第一个节点为虚节点(也叫做哨兵节点),其实不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的

acquireQueued()方法

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {.../*** Acquires in exclusive uninterruptible mode for thread already in* queue. Used by condition wait methods as well as acquire.** @param node the node* @param arg the acquire argument* @return {@code true} if interrupted while waiting*/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 GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}.../*** Checks and updates status for a node that failed to acquire.* Returns true if thread should block. This is the main signal* control in all acquire loops.  Requires that pred == node.prev.** @param pred node's predecessor holding status* @param node the node* @return {@code true} if thread should block*/private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;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.*/compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}.../*** Convenience method to park and then check if interrupted** @return {@code true} if interrupted*/private final boolean parkAndCheckInterrupt() {//挂起,等待唤醒LockSupport.park(this);return Thread.interrupted();}.../*** Cancels an ongoing attempt to acquire.** @param node the node*/private void cancelAcquire(Node node) {// Ignore if node doesn't existif (node == null)return;node.thread = null;// Skip cancelled predecessorsNode pred = node.prev;while (pred.waitStatus > 0)node.prev = pred = pred.prev;// predNext is the apparent node to unsplice. CASes below will// fail if not, in which case, we lost race vs another cancel// or signal, so no further action is necessary.Node predNext = pred.next;// Can use unconditional write instead of CAS here.// After this atomic step, other Nodes can skip past us.// Before, we are free of interference from other threads.node.waitStatus = Node.CANCELLED;// If we are the tail, remove ourselves.if (node == tail && compareAndSetTail(node, pred)) {compareAndSetNext(pred, predNext, null);} else {// If successor needs signal, try to set pred's next-link// so it will get one. Otherwise wake it up to propagate.int ws;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 {unparkSuccessor(node);}node.next = node; // help GC}}...}

12.4.4 方法 unlock()

三个方法sync.release(1)tryReleaseunparkSuccessor

public class ReentrantLock implements Lock, java.io.Serializable {.../*** Attempts to release this lock.** <p>If the current thread is the holder of this lock then the hold* count is decremented.  If the hold count is now zero then the lock* is released.  If the current thread is not the holder of this* lock then {@link IllegalMonitorStateException} is thrown.** @throws IllegalMonitorStateException if the current thread does not*         hold this lock*/public void unlock() {sync.release(1);}...}

sync.release(1) 方法

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {.../*** Releases in exclusive mode.  Implemented by unblocking one or* more threads if {@link #tryRelease} returns true.* This method can be used to implement method {@link Lock#unlock}.** @param arg the release argument.  This value is conveyed to*        {@link #tryRelease} but is otherwise uninterpreted and*        can represent anything you like.* @return the value returned from {@link #tryRelease}*/public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}.../*** Attempts to set the state to reflect a release in exclusive* mode.** <p>This method is always invoked by the thread performing release.** <p>The default implementation throws* {@link UnsupportedOperationException}.** @param arg the release argument. This value is always the one*        passed to a release method, or the current state value upon*        entry to a condition wait.  The value is otherwise*        uninterpreted and can represent anything you like.* @return {@code true} if this object is now in a fully released*         state, so that any waiting threads may attempt to acquire;*         and {@code false} otherwise.* @throws IllegalMonitorStateException if releasing would place this*         synchronizer in an illegal state. This exception must be*         thrown in a consistent fashion for synchronization to work*         correctly.* @throws UnsupportedOperationException if exclusive mode is not supported*/protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}   ...}

tryReleasee()采用模板设计模式,其实现方式在子类 ReentrantLock 中

public class ReentrantLock implements Lock, java.io.Serializable {...abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;...protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}...}...}

unparkSuccessor 方法

public abstract class AbstractQueuedSynchronizerextends AbstractOwnableSynchronizerimplements java.io.Serializable {.../*** Wakes up node's successor, if one exists.** @param node the node*/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)compareAndSetWaitStatus(node, 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.*/Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread);}...}

12.5 可重入的解读

AQS 实现可重入的三大核心机制

  1. state 变量作为重入计数器

  2. exclusiveOwnerThread 记录持有锁的线程

  3. 释放锁时的递减逻辑

state 变量作为重入计数器

// AQS 中的核心状态变量
private volatile int state;
  • state = 0:锁处于空闲状态,没有任何线程持有
  • state > 0:锁被某个线程持有,state 的值表示重入次数

对于 ReentrantLock 而言

  1. 第一次获取到锁:state 从 0 -> 1

  2. 第二次重入:state 从 1 -> 2

  3. 第 N 次重入:state 从 N-1 -> N

  4. 释放锁:state 从 N -> N-1,直到 state = 0 才完全释放

exclusiveOwnerThread 记录持有锁的线程

// AQS 继承自 AbstractOwnableSynchronizer
private transient Thread exclusiveOwnerThread;

这个字段记录了当前持有独占锁的线程,用于判断是否是重入:

protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();// 情况1: 锁空闲if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current); // 设置持有者return true;}}// 情况2: 锁已被当前线程持有(重入!)else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires; // state += 1if (nextc < 0) // 溢出检测throw new Error("Maximum lock count exceeded");setState(nextc); // 直接修改,不需要 CASreturn true;}return false; // 锁被其他线程持有,获取失败
}

关键点:

  • 如果 state > 0exclusiveOwnerThread == currentThread,说明是重入

  • 重入时直接setState(nextc),无需 CAS,因为当前线程已经持有锁,不存在并发竞争

释放锁时的递减逻辑

protected final boolean tryRelease(int releases) {int c = getState() - releases; // state -= 1// 只有当前持有锁的线程才能释放if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) { // 重入计数归零,真正释放锁free = true;setExclusiveOwnerThread(null); // 清空持有者}setState(c); // 更新 statereturn free;
}
  • 每次调用unlock()只减少一次计数
  • 只有当state == 0时,才将exclusiveOwnerThread置为 null,唤醒等待队列中的线程

这种设计既保证了线程安全,又避免了死锁

12.6 延迟中断的解读

AQS 中acquire()方法实现延迟响应中断

延迟响应中断完整流程图

线程 A 线程 B 中断信号
lock()后持有锁
lock()
tryAcquire() 失败
addWaiter()入队
acquireQueued() 自旋
shouldParkAfterFailedAcquire
parkAndCheckInterrupt
线程挂起
interrupt()
被中断唤醒
Thread.interrupted() = true
interrupted = true (记录)
selfInterrupt()
继续循环等待...
unlock()
再次 tryAcquire() 成功
setHead()
return interrupted (true)
selfInterrupt()
Thread.interrupt()恢复状态
执行业务逻辑(可检测中断)
unlock()

为什么要延迟响应中断?

  • ReentrantLock.lock()的设计目标是“一定要获取到锁”

  • 如果立刻响应中断,可能导致获取锁失败

  • 与 synchronized 的行为保持一致(synchronized 也不响应中断)

为什么不丢弃中断状态?

  • 中断是重要的线程间通信机制

  • 上层代码可能需要根据中断状态做清理工作

  • 符合 Java 并发包的设计哲学:不吞掉中断

selfInterrupt()的必要性

  • parkAndCheckInterrupt()中调用了Thread.interrupted()

  • 这个方法会消除中断标志

  • 所以需要事后恢复,让上层代码能感知到中断

实际应用场景

  • 使用lock()时,可以在获取锁后检查中断状态

  • 如果发现被中断,可以决定是否提前退出或做清理

  • 示例:

    lock.lock();
    try {if (Thread.interrupted()) {// 曾被中断,做清理工作cleanup();}// 正常业务逻辑
    } finally {lock.unlock();
    }
    

12.7 小总结

整个 ReentrantLock 的加锁过程,可以分为三个阶段:

  1. 尝试加锁

  2. 加锁失败,线程加入队列

  3. 线程入队列后,进入阻塞状态

http://www.jsqmd.com/news/697086/

相关文章:

  • DeepSeek-V4零样本适配政务文书解析
  • 2026年知乎写手必备:怕被限流?别踩AI检测的坑! - 降AI实验室
  • 分期乐额度回收常见问题汇总:解决变现难题,安全高效不踩坑 - 米米收
  • Diffusion噪声注入策略全解析:从均匀扰动到时变调制的核心方法
  • 从乐迪AT9S Pro到TX12 ELRS:我的四轴FPV遥控器血泪换装史与避坑指南
  • AI智能体代码安全执行:sandbox-agent沙盒环境架构与应用指南
  • 大润发购物卡回收渠道揭秘,教你轻松变现! - 团团收购物卡回收
  • 测试文章-2026-04-25 08:41:00
  • 行业盘点:TOP5强酸PVDF管材工厂,谁将引领技术新标准? - 品牌企业推荐师(官方)
  • Jetson Xavier NX的CAN口到底在哪?别再照着老教程瞎改了(附官方引脚图)
  • 手把手图解:用Python模拟信号传播与信道衰落,直观理解多径和OFDM
  • 优化CUDA程序必看:深入SM内部,搞懂Warp调度和Shared Memory如何影响你的核函数性能
  • 从STM32F103到GD32F303:一个真实项目的完整迁移日记(附代码对比与调试记录)
  • 如何快速提取视频硬字幕?本地化OCR解决方案完整指南
  • 大润发购物卡兑换攻略,轻松回收拿现金! - 团团收购物卡回收
  • 揭秘TOP3强酸PVDF法兰球阀源头工厂的硬核实力-苏一塑业 - 品牌企业推荐师(官方)
  • Phi-3.5-mini-instruct助力Git工作流:智能提交信息与代码审查
  • 从源码到实战:QtPropertyBrowser属性编辑器的现代化集成指南
  • 从Bind到Reverse:手把手教你理解并选择MSF中正确的Payload类型(附场景选择决策树)
  • 2026最新:盒马鲜生礼品卡回收的最佳线上平台 - 团团收购物卡回收
  • CN5120 宽输入电流模式升压直流-直流转换控制集成电路
  • React Context 状态管理方案对比
  • 别再手动转换了!C# WinForm + OpenCVSharp 4.x 实现 PictureBox 实时显示摄像头画面的保姆级教程
  • FortiGate SD-WAN实战:除了Ping和DNS,教你用HTTP检测自定义‘关键业务’的线路质量(比如电商访问亚马逊)
  • Voxtral-4B-TTS-2603算力优化:动态batch size自适应提升吞吐42%
  • 6G与AI原生网络:NVIDIA开发者日揭示通信技术未来
  • OptiSystem应用:数字调制-DPSK
  • 如何选择靠谱的线上平台快速回收盒马鲜生礼品卡? - 团团收购物卡回收
  • Java的java.util.HexFormat性能调优
  • STM32 HAL库实战:释放PB3-5和PA13-15引脚做I2C,别再被SWD/JTAG坑了