Java-AQS的原理

文章目录

      • 基本概述
      • 1. 设计思想
      • 2. 基本实现

一些关键词语以及常用术语,主要如下:

  • 信号量(Semaphore): 是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用,也是作系统用来解决并发中的互斥和同步问题的一种方法。
  • 信号量机制(Semaphores): 用来解决同步/互斥的问题的,它是1965年,荷兰学者 Dijkstra提出了一种卓有成效的实现进程互斥与同步的方法。 - 管程(Monitor) : 一般是指管理共享变量以及对共享变量的操作过程,让它们支持并发的一种机制。

基本概述

在Java领域中,我们可以将锁大致分为基于Java语法层面(关键词)实现的锁和基于JDK层面实现的锁。
image.png
在Java领域中, 尤其是在并发编程领域,对于多线程并发执行一直有两大核心问题:同步和互斥。其中:

  • 互斥(Mutual Exclusion):一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。即就是同一时刻只允许一个线程访问共享资源的问题。
  • 同步(Synchronization):两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。即就是线程之间如何通信、协作的问题。

虽然,Java在基于语法层面(synchronized 关键字)实现了对管程技术,但是从使用方式和性能上来说,内置锁(synchronized 关键字)的粒度相对过大,不支持超时和中断等问题。
为了弥补这些问题,从JDK层面对其“重复造轮子”,在JDK内部对其重新设计和定义,甚至实现了新的特性。
在Java领域中,从JDK源码分析来看,基于JDK层面实现的锁大致主要可以分为以下4种方式:

  • 基于Lock接口实现的锁
  • 基于ReadWriteLock接口实现的锁
  • 基于AQS基础同步器实现的锁
  • 基于自定义API操作实现的锁

从阅读源码不难发现,在Java SDK 并发包主要通过AbstractQueuedSynchronizer(AQS)实现多线程同步机制的封装与定义,而通过Lock 和 Condition 两个接口来实现管程,其中 Lock 用于解决互斥问题,Condition 用于解决同步问题。
在JDK源码中,同步器位于java.util.concurrent.locks包下,其基本定义是AbstractQueuedSynchronizer类,即就是我们常说的AQS同步器。

1. 设计思想

一个标准的AQS同步器主要有同步状态机制,等待队列,条件队列,独占模式,共享模式等五大核心要素组成。
image.png
JDK的JUC(java.util.concurrent.)包中提供了各种并发工具,但是大部分同步工具的实现基于AbstractQueuedSynchronizer类实现,其内部结构主要如下:

  • 同步状态机制(Synchronization Status):主要用于实现锁(Lock)机制,是指同步状态,其要求对于状态的更新必须原子性的
  • 等待队列(Wait Queue):主要用于存放等待线程获取到的锁资源,并且把线程维护到一个Node(节点)里面和维护一个非阻塞的CHL Node FIFO(先进先出)队列,主要是采用自旋锁+CAS操作来保证节点插入和移除的原子性操作。
  • 条件队列(Condition Queue):用于实现锁的条件机制,一般主要是指替换“等待-通知”工作机制,主要是通过ConditionObject对象实现Condition接口提供的方法实现。
  • 独占模式(Exclusive Mode):主要用于实现独占锁,主要是基于静态内部类Node的常量标志EXCLUSIVE来标识该节点是独占模式
  • 共享模式(Shared Mode):主要用于实现共享锁,主要是基于静态内部类Node的常量标志SHARED来标识该节点是共享模式

其中,对于AbstractQueuedSynchronizer类的实现原理,我们可以从如下几个方面来看:

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {

    private static final long serialVersionUID = 7373984972572414691 L;


    protected AbstractQueuedSynchronizer() {}

    /**
     * 等待队列: head-头节点
     */
    private transient volatile Node head;

    /**
     * 等待队列: tail-尾节点
     */
    private transient volatile Node tail;

    /**
     * 同步状态:32位整数类型,更新同步状态(state)时必须保证其是原子性的
     */
    private volatile int state;

    /**
     * 自旋锁消耗超时时间阀值(threshold): threshold < 1000ns时,表示竞争时选择自旋;threshold > 1000ns时,表示竞争时选择系统阻塞
     */
    static final long spinForTimeoutThreshold = 1000 L;

    /**
     * CAS原子性操作
     */
    private static final Unsafe unsafe = Unsafe.getUnsafe();

    /**
     * stateOffset
     */
    private static final long stateOffset;

    /**
     * headOffset
     */
    private static final long headOffset;

    /**
     * tailOffset
     */
    private static final long tailOffset;

    /**
     * waitStatusOffset
     */
    private static final long waitStatusOffset;

    /**
     * nextOffset
     */
    private static final long nextOffset;


    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);
        }
    }

            private final boolean compareAndSetHead(Node update)  {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

            private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

            private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

            private static final boolean compareAndSetNext(Node node,
                                                   Node expect,
                                                   Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }

            protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

}

[1]. AbstractQueuedSynchronizer类的实现原理是继承了基于AbstractOwnableSynchronizer类的抽象类,其中主要对AQS同步器的通用特性和方法进行抽象封装定义,主要包括如下方法:

public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {


    private static final long serialVersionUID = 3737899427754241961 L;


    protected AbstractOwnableSynchronizer() {}

    /**
     *  同步器拥有者
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * 设置同步器拥有者:把线程当作参数传入,指定某个线程为独享
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    /**
     * 获取同步器拥有者:获取指定的某个线程
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}
  • setExclusiveOwnerThread(Thread thread)方法: 把某个线程作为参数传入,从而设置AQS同步器的所有者,即就是我们设置的某个线程
  • getExclusiveOwnerThread()方法: 获取当前AQS同步器的所有者,即就是我们指定的某个线程

[2]. 对于同步状态(state),其类型是32位整数类型,并且是被volatile修饰的,表示在更新同步状态(state)时必须保证其是原子性的。
[3]. 对于等待队列的结构,主要是在Node定义了head和tail变量,其中head表示头部节点,tail表示尾部节点
[4].对于等待队列的结构提到的Node类来说,主要内容如下:

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 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
       */
      volatile Node prev;

      /**
       * Link to the successor node that the current node/thread
       */
      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
       */
      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();
          else
              return p;
      }

      Node() { // Used to establish initial head or SHARED marker
      }

      Node(Thread thread, Node mode) { // Used by addWaiter
          this.nextWaiter = mode;
          this.thread = thread;
      }

      Node(Thread thread, int waitStatus) { // Used by Condition
          this.waitStatus = waitStatus;
          this.thread = thread;
      }
  }
  • 标记Node的工作模式常量标记:主要维护了SHARED和EXCLUSIVE等2个静态字面常量,其中 SHARED 用于标记Node中是共享模式,EXCLUSIVE:用于标记Node中是独享模式
  • 标记等待状态的静态字面常量标记: 主要维护了0(表示无状态),SIGNAL(-1,表示后续节点中的线程通过park进入等待,当前节点在释放和取消时,需要通过unpark解除后后续节点的等待),CANCELLED(1,表示当前节点中的线程因为超时和中断被取消),CONDITION(-2,表示当前节点在条件队列中),PROPAGATE(-3,SHARED共享模式的头节点描述状态,表示无条件往下传播)等5个静态字面常量
  • 维护了一个等待状态(waitStatus): 主要用于描述等待队列中节点的状态,其取值范围为0(waitStatus=0,表示无状态),SIGNAL(waitStatus=-1,表示等待信号状态),CANCELLED(waitStatus=1,表示取消状态),CONDITION(waitStatus=-2,表示条件状态),PROPAGATE(waitStatus=-3,表示SHARED共享模式状态)等5个静态字面常量,CAS操作时写入,默认值为0。
  • 维护了Node的2个结构节点变量: 主要是prev和next,其中,prev表示前驱节点,next表示后续节点,表示构成双向向链表,构成了等待队列的数据结构
  • 维护了一个状态工作模式标记: 主要是维护了一个nextWaiter,用于表示在等待队列中当前节点在是共享模式还是独享模式,而对于条件队列来说,用于组成单向链表结构
  • 维护了一个线程对象变量: 主要用于记录当前节点中的线程thread

[5].对于自旋锁消耗超时时间阀值(spinForTimeoutThreshold),主要表示系统依据这个阀值来选择自旋方式还是系统阻塞。一般假设这个threshold,当 threshold < 1000ns时,表示竞争时选择自旋;否则,当threshold > 1000ns时,表示竞争时选择系统阻塞
[6].对于带有Offset 等变量对应各自的句柄,主要用于执行CAS操作。在JDK1.8版本之前,CAS操作主要通过Unsafe类来说实现;在JDK1.8版本之后,已经开始利用VarHandle来替代Unsafe类操作实现。
[7].对于CAS操作来说,主要提供了如下几个方法:

  • compareAndSetState(int expect, int update)方法:CAS操作原子更新状态
  • compareAndSetHead(Node update)方法:CAS操作原子更新头部节点
  • compareAndSetTail(Node expect, Node update)方法:CAS操作原子更新尾部节点
  • compareAndSetWaitStatus(Node node, int expect,int update)方法:CAS操作原子更新等待状态
  • compareAndSetNext(Node node,Node expect,Node update)方法:CAS操作原子更新后续节点

[8].除此之外,还包括许多辅助的操作方法,具体可参考源码分析。

2. 基本实现

一个标准的AQS同步器最核心底层设计实现是一个非阻塞的CHL Node FIFO(先进先出)队列数据结构,通过采用自旋锁+CAS操作的方法来保证原子性操作。
image.png

总的来说,一个AQS基础同步器,底层的数据结构采用的是一个非阻塞的CHL Node FIFO(先进先出)队列数据结构,而实现的核心算法则是采用自旋锁+CAS操作的方法。
首先,对于非阻塞的CHL Node FIFO(先进先出)队列数据结构,一般来说,FIFO(First In First Out,先进先出)队列是一个有序列表,属于抽象型数据类型(Abstract Data Type,ADT),所有的插入和删除操作都发生在队首(Front)和队尾(Rear)两端,具有先进先出的特性。

/**
     * 等待队列: head-头节点
     */
    private transient volatile Node head;

    /**
     * 等待队列: tail-尾节点
     */
    private transient volatile Node tail;

    /**
     * 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;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

    /**
     * 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 failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

在AQS同步器的源码中,主要是通过静态内部类Node来实现的这个非阻塞的CHL Node FIFO(先进先出)队列数据结构, 维护了两个变量head和tail,其中head对应队首(Front),tail对应队尾(Rear)。同时,还定义了addWaiter(Node mode)方法来表示入队操作,其中有个enq(final Node node)方法,主要用于初始化队列中head和tail的设置。
其次,AQS同步器以CLH锁为基础,其中CLH锁是一种自旋锁,对于自旋锁的实现方式来看,主要可以分为普通自旋锁和自适应自旋锁,CLH锁和MCS锁等4种,其中:

  • 普通自旋锁:多个线程不断自旋,不断尝试获取锁,其不具备公平性和由于要保证CPU和缓存以及主存之间的数据一致性,其开销较大。
  • 自适应自旋锁:主要是为解决普通自旋锁的公平性问题,引入了一个排队机制,一般称为排他自旋锁,其具备公平性,但是没有解决保证CPU和缓存以及主存之间的数据一致性问题,其开销较大。
  • CLH锁:通过一定手段将线程对于某一个共享变量的轮询竞争转化为一个线程队列,且队列中的线程各自轮询自己本地变量。
  • MCS锁:主旨在于解决 CLH锁的问题,也是基于FIFO队列,与CLH锁不同是,只对本地变量自旋,前驱节点负责通知MCS锁中线程自适结束。

自旋锁是一种实现同步的方案,属于一种非阻塞锁,与常规锁主要的区别就在于获取锁失败之后的处理方式不同,主要体现在:

  • 一般情况下,常规锁在获取锁失败之后,会将线程阻塞并适当时重新唤醒
  • 而自旋锁则是使用自旋来替换阻塞操作,主要是线程会不断循环检查该锁是否被释放,一旦释放线程便会获取锁资源。

从本质上讲,自旋是一钟忙等待状态,会一直消耗CPU的执行时间。一般情况下,常规互斥锁适用于持有锁长时间的情况,自旋锁适合持有时间短的情况。
其中,对于CLH锁来说,其核心是为解决同步带来的花销问题,Craig,Landim,Hagersten三人发明了CLH锁,其中主要是:

  • 构建一个FIFO(先进先出)队列,构建时主要通过移动尾部节点tail来实现队列的排队,每个想获得锁的线程都会创建一个新节点(next)并通过CAS操作原子操作将新节点赋予给tail,当前线程轮询前一个节点的状态。
  • 执行完线程后,只需将当前线程对应节点状态设置为解锁即可,主要是判断当前节点是否为尾部节点,如果是直接设置尾部节点设置为空。由于下一个节点一直在轮询,所以可以获得锁。

CLH锁将众多线程长时间对资源的竞争,通过有序化这些线程将其转化为只需要对本地变量检测。唯一存在竞争的地方就是入队之前对尾部节点tail 的竞争,相对来说,当前线程对资源的竞争次数减少,这节省了CPU缓存同步的消耗,从而提升了系统性能。
但是同时也有一个问题,CLH锁虽然解决了大量线程同时操作同一个变量时带来的开销问题,如果前驱节点和当前节点在本地主存中不存在,则访问时间过长,也会引起性能问题。
为了让CLH锁更容易实现取消和超时的功能,AQS同步器在设计时进行了改造,主要体现在:节点的结构和节点等待机制。其中:

  • 节点的结构: 主要引入了头节点和尾节点,分别指向队列头部和尾部,对于锁的相关操作都与其息息相关,并且每个节点都引入了前驱节点和后继节点。
  • 节点等待机制: 主要在原来的自旋基础上增加了系统阻塞唤醒,主要体现在 自旋锁消耗超时时间阀值(threshold): threshold < 1000ns时,表示竞争时选择自旋;threshold > 1000ns时,表示竞争时选择系统阻塞。

由此可见,主要是通过前驱节点和后继节点的引用连接起来形成一个链表队列,其中对于入队,检测节点,出队,判断超时,取消节点等操作主要如下:

  • 入队(enqueue): 主要采用一个无限循环进行CAS操作,即就是使用自旋方式竞争直到成功。
  • 检测节点(checkedPrev): 一般在入队完成后,主要是检测判断当前节点的前驱节点是否为头节点, 一般自旋方式是直接进入循环检测,而系统阻塞方式是当前线程先检测,其中如果是头节点并成功获取锁,则直接返回,当前线程不阻塞,否则对当前线程进行阻塞。
  • 出队(dequeue):主要负责唤醒等待队列中的后继节点,并且按照条件往下传播有序执行
  • 判断超时(checkedTimeout): 队列中等待锁的线程可能因为中断或者超时的情况,当总耗时大于等于自定义耗时就直接返回,即就是
  • 取消节点(cancel): 主要是对于中断和超时而涉及到取消操作,而且这样的情况不再参与锁竞争,即就是一般通过调用compareAndSetNext(Node node, Node expect,Node update)来进行CAS操作。

最后,AQS同步器中使用了CAS操作,其中CAS(Compare And Swap,比较并交换)操作时一种乐观锁策略,主要涉及三个操作数据:内存值,预期值,新值,主要是指当且仅当预期值和内存值相等时才去修改内存值为新值。
一般来说,CAS操作的具体逻辑,主要可以分为三个步骤:

  • 首先,检查某个内存值是否与该线程之前取到值一样。
  • 其次,如果不一样,表示此内存值已经被别的线程修改,需要舍弃本次操作。
  • 最后,如果时一样,表示期间没有线程更改过,则需要用新值执行更新内存值。

除此之外,需要注意的是CAS操作具有原子性,主要是由CPU硬件指令来保证,并且通过Java本地接口(Java Native Interface,JNI)调用本地硬件指令实现。

  • 当然,CAS操作避免了悲观策略独占对象的 问题,同时提高了并发性能,但是也有以下三个问题:乐观策略只能保证一个共享变量的原子操作,如果是多个变量,CAS便不如互斥锁,主要是CAS操作的局限所致。
  • 长时间循环操作可能导致开销过大。
  • 经典的ABA问题: 主要是检查某个内存值是否与该线程之前取到值一样,这个判断逻辑不严谨。解决ABA问题的核心在于,引入版本号,每次更新变量值更新版本号。

而在AQS同步器中,为了保证并发实现保证原子性,而且是硬件级别的原子性,一般是通过JNI(Java native interface,Java 本地接口)方式让Java代码调用C/C++本地代码。
需要注意的是,在Java领域中,对于CAS操作实现,主要有亮点问题:

  • JDK1.8版本之前,CAS操作主要使用Unsafe类来执行底层操作,一般并发和线程操作时,主要用compareAndSwapObject,compareAndSwapInt,compareAndSwapLong等来实现CAS,而对于线程调度主要是park和unpark方法,其主要在sun.misc包下面。
  • JDK1.8版本之后,JDK1.9的CAS操作主要使用VarHandle类,只是用VarHandle替代了一部分Unsafe类的操作,但是对于新版本中Unsafe,本质上Unsafe类会间接调用jdk.internal.misc包下面Unsafe类来实现。


唤醒节点
注意for循环中的逻辑:unparkSuccessor(Node) 从尾部开始向前遍历,找到最前的一个处于正常阻塞状态的结点,直到节点重合(即等于当前节点) 。 为什么要从尾部开始向前遍历?带着疑问继续往下看…

/**
     * 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)  // 最近非取消状态节点(CANCELLED = 1)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

高并发下入队逻辑
既然采用了从尾部遍历的逻辑,那么肯定是为了解决可能会出现的问题。而这个问题就在enq(…)方法中

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;   							     // ①
            //set尾部节点
            if (compareAndSetTail(t, node)) {//当前节点置为尾部   ②
                t.next = node; //前驱节点的next指针指向当前节点    ③
                return t;
            }
        }
    }
}

enq 方法都是没有进行存在并发安全的问题。 原 CLH 算法并没有 next 引用, Doug Lea 在此做出了优化, 
但是不保证一个结点通过 next 引用一定能其后继结点. 可以理解为一次快速尝试. 但是由于 prev 是可靠的, 
    因而我们一定能通过从 tail 开始反向遍历的方式找到一个结点.

① 处将新结点 node 的 prev 引用指向当前的 t, 即 tail 结点. 然而, 由于 ①, ② 这两行代码的合在一起并非原子性的, 
所以很有可能在设置 tail 时存在着竞争, 也即 tail 被其它线程更新过了. 所以要自旋操作, 即在死循环中操作, 直到成功为止. 
自旋地 CAS volatile 变量是很经典的用法. 如果设置成功了, 那么 从 node.prev 执行完毕到正在用 CAS 设置 tail 时, 
tail 变量是没有被修改的, 所以如果 CAS成功, 那么 node.prev = t 一定是指向上一个 tail 的. 
同样的,, ③ 合在一起也并非原子操作, 更重要的是, next field 的设置发生在 CAS 操作之后, 所以可能会存在 tail 已经更新, 
但是 last tail 的 next field 还未设置完毕, 即它的 lastTail.next 为 null 这种情况. 
因此如果此时访问该结点的 next 引用可能就会得到它在队尾, 不存在后继结点; 所以上面释放锁是从tail开始循环查找的

compareAndSetTail(t, node) 假如设置成功, t节点中的变量还是原来的数据值还没有发生变化。还是上一个尾节点的数据值 

源码: java.util.concurrent.locks.AbstractQueuedSynchronizer.Node
    /**
     * 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;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/573933.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据挖掘 | Count数据去除批次效应后不是整数甚至还出现负值导致无法进行差异分析怎么办?

之前咱们介绍过数据挖掘 | 批次效应的鉴定与处理 | 附完整代码 注释 | 看完不会来揍我&#xff0c;但是很多小伙伴遇到了Count数据批次处理后不是整数甚至还出现负值的问题&#xff0c;这就导致无法使用某些包包进行差异分析&#xff08;对差异分析感兴趣的小伙伴可以查看&…

MySQL中如何随机获取一条记录

点击上方蓝字关注我 随机获取一条记录是在数据库查询中常见的需求&#xff0c;特别在需要展示随机内容或者随机推荐的场景下。在 MySQL 中&#xff0c;有多种方法可以实现随机获取一条记录&#xff0c;每种方法都有其适用的情况和性能特点。在本文中&#xff0c;我们将探讨几种…

word添加行号

打开页面设置&#xff0c;找到行号

2018-2023年上市公司富时罗素ESG评分数据

2018-2023年上市公司富时罗素ESG评分数据 1、时间&#xff1a;2018-2023年 2、来源&#xff1a;整理自WIND 3、指标&#xff1a;证券代码、简称、ESG评分 4、范围&#xff1a;上市公司 5、指标解释&#xff1a; 富时罗素将公司绿色收入的界定和计算作为公司ESG 评级打分结…

「白嫖」开源的后果就是供应链攻击么?| 编码人声

「编码人声」是由「RTE开发者社区」策划的一档播客节目&#xff0c;关注行业发展变革、开发者职涯发展、技术突破以及创业创新&#xff0c;由开发者来分享开发者眼中的工作与生活。 面对网络安全威胁日益严重的今天&#xff0c;软件供应链安全已经成为开发者领域无法避免的焦点…

OpenWRT设置自动获取IP,作为二级路由器

前言 上一期咱们讲了在OpenWRT设置PPPoE拨号的教程&#xff0c;在光猫桥接的模式下&#xff0c;OpenWRT如果不设置PPPoE拨号&#xff0c;就无法正常上网。 OpenWRT设置PPPoE拨号教程 但现在很多新装的宽带&#xff0c;宽带师傅为了方便都会把光猫设置为路由模式。如果你再外…

【A-024】基于SSH的房屋租赁管理系统(含论文)

【A-024】基于SSH的房屋租赁管理系统&#xff08;含论文&#xff09; 开发环境&#xff1a; Jdk7(8)Tomcat7(8)MySQLIntelliJ IDEA(Eclipse) 数据库&#xff1a; MySQL 技术&#xff1a; SpringStruts2HiberanteBootstrapJquery 适用于&#xff1a; 课程设计&#xff0c;毕…

半波整流220V转正5V负-5V100mA恒压WT5101A

半波整流220V转正5V负-5V100mA恒压WT5101A WT5101A 是一款专为 Buck 和 Buck-Boost 拓扑而设计的高效、具有成本优势的离线恒压稳压器&#xff0c;内嵌有500V MOSFET。在降低系统成本的同时&#xff0c;这款稳压器只需少量的外部元件就能输出默认的5V电压。在轻负载条件下&…

Sping源码(七)—context: component-scan标签如何扫描、加载Bean

序言 简单回顾一下。上一篇文章介绍了从xml文件context component-scan标签的加载流程到ConfigurationClassPostProcessor的创建流程。 本篇会深入了解context component-scan标签底层做了些什么。 component-scan 早期使用Spring进行开发时&#xff0c;很多时候都是注解 标…

智能算法 | Matlab基于CBES融合自适应惯性权重和柯西变异的秃鹰搜索算法

智能算法 | Matlab基于CBES融合自适应惯性权重和柯西变异的秃鹰搜索算法 目录 智能算法 | Matlab基于CBES融合自适应惯性权重和柯西变异的秃鹰搜索算法效果一览基本介绍程序设计参考资料效果一览 基本介绍 Matlab基于CBES融合自适应惯性权重和柯西变异的秃鹰搜索算法 融合自适应…

ds18b20温度传感器驱动程序

ds18b20驱动程序 有了之前延时的方法&#xff0c;那么实现一个单总线数据传输的传感器驱动程序就非常简单了。下面我们套用杂项驱动框架来编写ds18b20驱动程序。 实现需要明确的是&#xff1a;**ds18b20驱动的本质是通过2440的gpio&#xff0c;通过给定的时序对ds18b20的读写数…

【介绍下WebStorm开发插件】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

保护你的网站:了解5种常见网络攻击类型及其防御方法

随着互联网的迅猛发展&#xff0c;针对网站的各种类型的网络攻击随之增加&#xff0c;网络攻击事件层出不穷&#xff0c;由此&#xff0c;如何保护网站安全成为每个网站所有者的重要议题。在下面的内容中&#xff0c;我们将探讨5种常见网络攻击类型及其防御方法&#xff0c;以帮…

SNETCracker--超级弱口令检查工具简介

一、简介 SNETCracker 超级弱口令检查工具是一款Windows平台的弱口令审计工具&#xff0c;支持批量多线程检查&#xff0c;可快速发现弱密码、弱口令账号&#xff0c;密码支持和用户名结合进行检查&#xff0c;大大提高成功率&#xff0c;支持自定义服务端口和字典。 二、SNE…

常见内网系统网络结构及nginx代理配置

系统网络结构图及nginx配置 1.系统网络结构图2.Nginx网络配置2.1请求从互联网区访问到内网区2.2 请求从内网访问互联网 1.系统网络结构图 传统公司服务部署网络都会分区&#xff0c;应用都部署在内网区&#xff0c;请求通过dmz区转出内网与互联网发生交互。 结构图详解&#…

springCloud集成activiti5.22.0流程引擎

springCloud集成activiti5.22.0流程引擎 点关注不迷路&#xff0c;欢迎再访&#xff01; 精简博客内容&#xff0c;尽量已行业术语来分享。 努力做到对每一位认可自己的读者负责。 帮助别人的同时更是丰富自己的良机。 小编最近工作需要涉及到流程&#xff0c;由于网络上5.22版…

CHARLS轻松发二区,只用了COX回归模型 | CHARLS CLHLS CFPS 公共数据库周报(4.3)...

零基础CHARLS发论文&#xff0c;不容错过&#xff01; 长期回放更新指导&#xff01;适合零基础&#xff0c;毕业论文&#xff0c;赠送2011-2020年CHARLS清洗后的数据全套代码&#xff01; CHARLS公共数据库 CHARLS数据库简介中国健康与养老追踪调查(China Health and Retireme…

C++初阶学习第三弹——类与对象(上)——初始类与对象

前言&#xff1a; 在前面&#xff0c;我们已经初步学习了C的一些基本语法&#xff0c;比如内敛函数、函数重载、缺省参数、引用等等&#xff0c;接下来我们就将正式步入C的神圣殿堂&#xff0c;首先&#xff0c;先给你找个对象 目录 一、类与对象是什么&#xff1f; 二、类的各…

ArtNeRF、Attention Control、Pixel is a Barrier、FilterPrompt

本文首发于公众号&#xff1a;机器感知 ArtNeRF、Attention Control、Pixel is a Barrier、FilterPrompt ArtNeRF: A Stylized Neural Field for 3D-Aware Cartoonized Face Synthesis Recent advances in generative visual models and neural radiance fields have greatly …

【笔试训练】day11

1.游游的水果大礼包 思路&#xff1a; 枚举。假设最后的答案是x个a礼包&#xff0c;y个b礼包&#xff0c;得到一个式子&#xff1a;ansa*xb*y 我们可以枚举x的数量&#xff0c;这样就能变相的把y的求出来。呃这就是鸡兔同笼问题嘛 x最大的范围是多少呢&#xff1f;也就是a礼…
最新文章