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

JUC集合类 SynchronousQueue源码解析 JDK8

文章目录

  • 前言
  • Transferer抽象类
  • TransferStack
    • 节点成员
    • 节点类型
    • TransferStack成员
    • transfer方法
    • awaitFulfill
    • clean
  • TransferQueue
    • 节点成员
    • 节点类型
    • TransferQueue成员
    • transfer方法
    • awaitFulfill
    • clean
  • 无效操作
  • 总结

前言

SynchronousQueue其实就是LinkedTransferQueue的升级版,相同的是它们都作为生产者和消费者交互的通道,可以直接让生产者和消费者打交道。不同的是,SynchronousQueue做的更彻底,不去支持无关的共有操作(比如size()),只提供必要的入队出队方法。并且,SynchronousQueue提供了两种逻辑结构,栈和队列。

理解LinkedTransferQueue是搞懂SynchronousQueue的前提。

JUC框架 系列文章目录

Transferer抽象类

    abstract static class Transferer<E> {abstract E transfer(E e, boolean timed, long nanos);}

这里的transfer方法与LinkedTransferQueue的不同。按照LinkedTransferQueue的话,不管是data node还是request node,如果transfer成功返回的是创建节点时的item的相反值,如果transfer失败返回的是创建节点时的item的相同值(比如创建了data node的线程,data node的item初始是非null的,如果交易成功,则返回null;如果交易失败,则返回非null),相反相同指的是 null 或 非null。

但SynchronousQueue的transfer方法则不一样,不管是data node还是request node,如果transfer成功返回的是非null,如果transfer失败返回的是null。从子类实现中,我们可以看到这一点。

TransferStack

内部逻辑是栈的形式,让节点入队出队都从链表的同一端操作。

节点成员

        static final class SNode {volatile SNode next;        // 单链表的next指针volatile SNode match;       // 如果交易成功,那么交易先到的node的match成员,会指向交易后到的nodevolatile Thread waiter;     // 阻塞前保存当前线程,用来被unparkObject item;                // data node为非null;request node为nullint mode;                   // 有四种情况,下面介绍

item不需要是volatile的,因为创建节点之后总是会有CAS操作。

节点类型

根据mode的值,有四种节点。

        static final int REQUEST    = 0;static final int DATA       = 1;static final int FULFILLING = 2;
  • mode == REQUEST0b00。交易先到的request node。
  • mode == DATA0b01。交易先到的data node。
  • mode == FULFILLING | REQUEST0b10。交易后到的request node。交易后到的都属于fulfilling node。
  • mode == FULFILLING | DATA0b11。交易后到的data node。交易后到的都属于fulfilling node。

我们称request node和data node是两个相反模式的节点。

TransferStack成员

volatile SNode head;  // 因为是栈结构,所以只需要用一个指针来保存栈顶

没有找到构造器,所以是默认构造器。初始时head为null。

transfer方法

E transfer(E e, boolean timed, long nanos) {SNode s = null; // constructed/reused as neededint mode = (e == null) ? REQUEST : DATA;for (;;) {SNode h = head;// 队列为空或队首模式相同(这说明队列中只有模式相同的节点)// 注意,h.mode == mode已经排除掉了两种fulfilling node(data的、或request的)if (h == null || h.mode == mode) {  //如果是计时操作,且传入时间参数<=0(一次尝试)if (timed && nanos <= 0) {      // 非计时操作或计时操作已经超时if (h != null && h.isCancelled())casHead(h, h.next);     // 通过修改head出队取消节点elsereturn null;//1. 如果不是计时操作(同步操作)//2. 如果是计时操作,且传入时间参数>0(同步带超时操作)} else if (casHead(h, s = snode(s, e, h, mode))) {SNode m = awaitFulfill(s, timed, nanos);//返回的是s的匹配对象if (m == s) {  // 如果匹配对象指向自身,说明当前线程被取消(中断或超时)clean(s);  // 尝试将s从栈中移除return null;}// 如果匹配对象不是指向自身,说明当前线程等到了匹配对象的到来if ((h = head) != null && h.next == s)casHead(h, s.next);     // 将匹配的一对节点出队return (E) ((mode == REQUEST) ? m.item : s.item);}//队首为相反模式节点、或两种fulfilling node//如果不是fulfilling node,那么队首为相反模式节点} else if (!isFulfilling(h.mode)) { // 那么进行配对if (h.isCancelled())            // 如果是取消节点casHead(h, h.next);         // 移除取消节点else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {//尝试入栈用来匹配的fulfill节点for (;;) { // loop until matched or waiters disappearSNode m = s.next;       if (m == null) {        // 如果发现没有匹配对象casHead(s, null);   // 清空整个栈s = null;           // 清空s因为旧s的成员不对break;              // 下一次循环将作为普通节点再入队}SNode mn = m.next;  //链表结构s -> m -> mnif (m.tryMatch(s)) {//匹配成功casHead(s, mn);     // 将匹配的一对节点出队return (E) ((mode == REQUEST) ? m.item : s.item);} else                  // 如果m是个取消节点s.casNext(m, mn);   // 移除这个取消节点}}//剩下的情况都是fulfilling node} else {                            // 那么帮忙出队匹配的一对SNode m = h.next;               if (m == null)                  // 如果发现没有匹配对象casHead(h, null);           // 清空整个栈else {SNode mn = m.next;if (m.tryMatch(h))          // 进行匹配casHead(h, mn);         // 将匹配的一对节点出队else                        // 如果m是个取消节点h.casNext(m, mn);       // 移除这个取消节点}}}
}

之前讲过节点有4种mode,现在transfer方法就根据这4种mode分为了三种处理情况:

  1. 队列为空,或者队首是一个相同模式的节点(非fulfilling节点)。当前线程是交易先到的一方。接下来根据调用参数做不同处理:
    1. timed == true && nanos <= 0。从字面意思就是这是个计时操作但却等0纳秒,其实就是只做一次尝试,如果只能找到相同模式节点的话,那么当前线程就不可能匹配成功,那就只能返回null代表失败了。
    2. timed == false。不是计时操作,那就是一个同步操作。这种情况,如果只能找到相同模式节点的话,那么让当前线程创建节点入队,阻塞等待匹配的对方到来。从awaitFulfill函数返回只能因为 匹配成功、被中断。
    3. timed == true && nanos > 0。从字面意思就是一个正常的计时操作。这种情况,如果只能找到相同模式节点的话,那么让当前线程创建节点入队,等待匹配的对方到来。从awaitFulfill函数返回只能因为 匹配成功、被中断、超时。
  2. 队首是一个相反模式的节点(非fulfilling节点)。当前线程是交易后到的一方。那么入队fulfill节点进行匹配,匹配如果成功(tryMatch返回true,且会唤醒交易先到的一方的线程),那么出队匹配的一对节点。
  3. 队首是一个fulfilling节点。那么帮忙出队匹配的一对节点。
            boolean tryMatch(SNode s) {if (match == null &&UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {Thread w = waiter;if (w != null) {    // waiters need at most one unparkwaiter = null;LockSupport.unpark(w);//唤醒交易先到的一方的线程}return true;}return match == s;//匹配失败}

这种加入fulfilling节点的手法类似于ConcurrentSkipListMap源码中的marker。在ConcurrentSkipListMap中,要删除链表中一个节点,不是直接将其移除,而是在被删除节点之前插入一个marker,当marker插入成功后,才将marker和被删除节点一起从链表中移除,这种做法保证了无锁实现能正确执行。而fulfilling节点的作用其实和marker是一样的。

整个执行过程能够保证链表中的非fulfilling节点肯定只有一种,要么是request node,要么是data node。

之前提到transfer方法的返回值是成功返回非null,失败返回null。但是不管当前线程在交易中先到还是后到,执行的总是return (E) ((mode == REQUEST) ? m.item : s.item),这里我解释一下为什么。
在这里插入图片描述
从上图可知,其实是因为不管哪种情况,s总是指向当前线程创建的节点,m总是指向匹配对方创建的节点,而这两个节点一个是request node一个是data node,既然是这样的话,根据s的模式就能知道这一对节点中哪个是data node,然后返回data node的item即可。所以永远执行return (E) ((mode == REQUEST) ? m.item : s.item)

在交易成功后,双方线程在尝试出栈时其实是做的同一个操作,先到线程执行的casHead(h, s.next),和后到线程执行的casHead(s, mn),从上图也可知,这两个CAS操作的两个参数其实是一模一样的,所以这两个CAS操作只有一个能成功,另一个直接失败就好。这种类似的,互相帮忙式的操作很多,比如clean(s)s.casNext(m, mn),都是在移除取消节点,前者被移除节点s就是当前线程创建的,后者被移除节点m不是当前线程创建的,而这后者就属于在帮忙。

另外,我们的移除操作是这样的。
在这里插入图片描述

        static SNode snode(SNode s, Object e, SNode next, int mode) {if (s == null) s = new SNode(e);s.mode = mode;s.next = next;return s;}

当前线程在创建节点时使用snode,这个函数可以使得创建出来的节点可以被重复利用。

awaitFulfill

此函数返回当前线程创建节点的匹配节点。不过也可能返回的是自身节点,如果当前线程被中断或超时。总之,此函数只会返回 非null值。

SNode awaitFulfill(SNode s, boolean timed, long nanos) {//s肯定是当前线程创建的节点final long deadline = timed ? System.nanoTime() + nanos : 0L; Thread w = Thread.currentThread();//计算出自旋次数int spins = (shouldSpin(s) ?//当head还是s,或head是一个fulfiller节点时,shouldSpin返回true(timed ? maxTimedSpins : maxUntimedSpins) : 0);for (;;) {if (w.isInterrupted())//如果被中断,则取消自身s.tryCancel();SNode m = s.match;if (m != null)return m;  //有可能是正常被匹配到了,也有可能因为自身被取消。if (timed) {// 如果是超时操作nanos = deadline - System.nanoTime();if (nanos <= 0L) {//如果剩余时间没有了s.tryCancel();//取消自身continue;//下一次循环退出函数}}if (spins > 0)//如果自旋次数还有,那么减一。如果发现没必要自旋了,那么归零spins = shouldSpin(s) ? (spins-1) : 0;else if (s.waiter == null)//自旋次数为0后,这次循环先放置当前线程s.waiter = w; //自旋次数为0,且当前线程已经放置好了else if (!timed)//如果是非超时操作,无限阻塞LockSupport.park(this);else if (nanos > spinForTimeoutThreshold)//如果是超时操作,执行超时版本阻塞LockSupport.parkNanos(this, nanos);//如果是超时操作,但时间太短,那么即使spins为0,也不停自旋直到时间消耗完}
}
  • 在有必要自旋时(shouldSpin),先进行自旋,因为当前线程可能在自旋过程中就发现自己被匹配了。
  • 自旋结束前,设置Thread成员。
  • 根据参数,调用不同版本的park进行阻塞。
  • 当从阻塞中唤醒时,检查被唤醒的原因。
    • 如果是因为当前线程被中断,那么取消自身节点,返回自身。
    • 如果是因为被正常匹配,那么返回匹配到的节点。
    • 如果是因为超时,那么取消自身节点,返回自身。

自旋次数为0后,当前循环先放置当前线程作为s成员,下一次循环再检查一次if (m != null)看看是否被匹配了,尽可能避免不必要的阻塞,尽量在自旋过程中就发现自身节点被匹配到了。

        boolean shouldSpin(SNode s) {SNode h = head;//中间的h == null只是严谨的防止空指针异常return (h == s || h == null || isFulfilling(h.mode));}

shouldSpin函数返回真说明当前线程有必要自旋。

  • 第一种情况h == s说明当前线程还在栈顶,那么继续自旋等待。
  • 第二种情况h是个fulfill节点,这说明有可能是s正在被匹配当前,那么也继续自旋等待。
  • 第三章情况h != s且不是fulfill节点,这说明没有必要等了,因为要匹配也是先匹配栈顶节点。

clean

此函数尝试从栈中移除掉s节点,就算s节点就是head,也会将其移除。

        void clean(SNode s) {//s肯定是个取消节点s.item = null;   // forget items.waiter = null; // forget threadSNode past = s.next;//找到一个s的后继if (past != null && past.isCancelled())//如果后继是取消节点past = past.next;//则获得后继的后继// 从head开始清除取消节点,直到遇到非取消节点或者pastSNode p;while ((p = head) != null && p != past && p.isCancelled())casHead(p, p.next);//结束循环时,p可能为://1. null//2. past(这说明past之前都是取消节点)//3. 第一个非取消节点// 这个循环遇到past就停止,过程中一直在移除取消节点,当遇到past说明s已经被移除了// 注意这个循环只能清除p以后的取消节点,p本身如果是取消节点则无能为力while (p != null && p != past) {SNode n = p.next;if (n != null && n.isCancelled())p.casNext(n, n.next);elsep = n;}}
  • 找到一个s的后继past。
  • 找到一个遍历起点p,这个p可能就是past,也可能是非past的一个非取消节点。
  • 从遍历起点p进行遍历直到遇到past,遍历过程一直在移除取消节点。原理就是,如果遍历已经遇到了past,那么作为取消节点的s就一定已经被移除掉了。

最坏情况下,s处于栈底,那么需要遍历整个链表。

在这里插入图片描述
之前提到,在并发情况下,有可能别的线程已经帮忙把s移除了,所以遍历过程中如果以s作为寻找标准,有可能永远也找不着。根据上图可知,如果在遍历过程中发现了s的后继,那么也说明了s被移除了。所以此函数使用了past这种东西。

TransferQueue

内部逻辑是队列的形式,让节点入队出队从链表的各自两端操作。

整体上和LinkedTransferQueue很像,交易后到的一方不会真正加入fulfill节点,而是直接修改交易先到的节点的item域。但本队列初始时有dummy node。

节点成员

        static final class QNode {volatile QNode next;          // 单链表的next指针volatile Object item;         // 创建的是request node,为null;创建的是data node,为非nullvolatile Thread waiter;       // 阻塞前保存当前线程,用来被unparkfinal boolean isData;         // 两种模式,request node或data node

注意,不再需要match成员了,因为TransferQueue实际操作中并没有fulfill节点存在,而是直接修改匹配对方节点的item。

节点类型

节点只有两种:

  • data node:isData为true。
  • request node:isData为false。

称上面两种节点模式相反。

节点的item域各个时期不同:
data node:

  • 刚创建时:item域为非null。
  • 被交易后到的一方匹配到:item域为null。
  • 自身取消:item域指向自身。

request node:

  • 刚创建时:item域为null。
  • 被交易后到的一方匹配到:item域为非null。
  • 自身取消:item域指向自身。

TransferQueue成员

        transient volatile QNode head;//队首transient volatile QNode tail;//队尾transient volatile QNode cleanMe;//clean方法中使用,用来保存 需要延后移除的节点的前驱
        TransferQueue() {QNode h = new QNode(null, false); // initialize to dummy node.head = h;tail = h;}

构造器创建一个dummy node,虽然它和一个正常的request node一样,但执行过程通过head == tail来判断队列为空,所以不必担心。
队列为空时,head和tail指向同一个节点。真正的节点永远是head后继。

transfer方法

同样的,transfer方法在匹配成功时返回非null值,匹配失败时返回null值。

        E transfer(E e, boolean timed, long nanos) {QNode s = null; // constructed/reused as neededboolean isData = (e != null);for (;;) {QNode t = tail;QNode h = head;if (t == null || h == null)         // 如果看到了队列未初始化的值continue;                       // 继续循环if (h == t || t.isData == isData) { // 因为有dummy node,所以h == t代表队列空;队尾是相同模式QNode tn = t.next;if (t != tail)                  // 发现tail已经更新,那么继续循环continue;                   // 这里如果不继续,接下来的t.casNext(null, s)也会失败的if (tn != null) {               // 虽然t为tail但它却有后继,说明遇到了别的线程添加节点后还没advanceTail(t, tn);         // 更新tail的中间状态。帮忙更新tail。continue;}if (timed && nanos <= 0)        // 如果是一次尝试版本,且发现队尾都是相同模式return null;                // 说明尝试失败,返回null//其他情况就是,同步版本和超时版本if (s == null)                  // 节点只创建一次s = new QNode(e, isData);if (!t.casNext(null, s))        // 入队失败,被别的线程抢先了continue;//入队成功advanceTail(t, s);              // 更新tail为最新入队节点Object x = awaitFulfill(s, e, timed, nanos);if (x == s) {                   // 当前线程的节点被取消而唤醒(中断或超时)clean(t, s);return null;}// 当前线程的节点是正常匹配而唤醒if (!s.isOffList()) {           // 如果s还没有自链接advanceHead(t, s);          // 使head从t变成sif (x != null)              // 如果awaitFulfill返回非null,说明匹配对方是生产者s.item = s;             // s节点为消费者,但s的item已变成非null,所以要释放掉这个引用s.waiter = null;            // 线程对象的引用一定要释放}return (x != null) ? (E)x : e;// 队尾是相反模式} else {                            QNode m = h.next;               // 如果队尾是相反模式,说明head后继也是相反模式if (t != tail || m == null || h != head) // 队列结构已经和刚才看到的不一样了continue;                   Object x = m.item;if (isData == (x != null) ||    // 参数isData与匹配对方节点的item域情况相同,说明对方已经被匹配x == m ||                   // item域指向自身,说明匹配对方节点被取消!m.casItem(x, e)) {         // 修改匹配对方节点的item域失败,说明对方已经 被匹配或取消advanceHead(h, m);          // 由于真正节点在head之后,所以head移动到m相当于出队mcontinue;                   // 以上三种情况,m节点都不能被当前线程匹配了,需要继续循环}//执行到这里,说明上面的m.casItem(x, e)成功了,即配对成功advanceHead(h, m);              // 匹配成功后,也使得m出队LockSupport.unpark(m.waiter);   // 唤醒交易先到的一方return (x != null) ? (E)x : e;}}}

处理过程更加简单,因为交易后到的一方不需要添加fulfill节点,而是直接匹配:

  1. 队列为空,或者队尾是一个相同模式的节点(这说明队列中只有相同模式的节点)。那么当前线程是交易先到的一方。接下来根据调用参数做不同处理:
    1. timed == true && nanos <= 0。做一次尝试,但既然现在队列中只有相同模式节点,那么尝试匹配失败,直接返回null。
    2. timed == false。不是计时操作,那就是一个同步操作。虽然现在队列中只有相同模式节点,那么让当前线程创建节点入队,阻塞等待匹配的对方到来。从awaitFulfill函数返回只能因为 匹配成功、被中断。
    3. timed == true && nanos > 0。计时操作。那么让当前线程创建节点入队,有时间限制得阻塞等待匹配的对方到来。从awaitFulfill函数返回只能因为 匹配成功、被中断、超时。
  2. 队尾是一个相反模式的节点(这说明head后继也是相反模式的节点)。当前线程是交易后到的一方。尝试修改交易先到一方的节点(head后继)的item域,如果修改成功,代表匹配成功,那么唤醒交易先到一方线程。注意,交易后到的一方并没有创建节点。

整个执行过程能够保证链表中,属于刚创建状态的node肯定都是相同模式的。

上面两种情况,都是执行return (x != null) ? (E)x : e,这能保证总是返回 匹配一对中的data node的初始item域。

  • 上两种情况中,x都代表交易对方的初始item。
    • 如果当前线程进入的if (h == t || t.isData == isData)else分支。x就是交易对方节点的初始item域。另外,它会将交易对方节点的item域,修改为自己节点的item(虽然它并没有真正创建节点)。
    • 如果当前线程进入的if (h == t || t.isData == isData)分支。当匹配成功后从awaitFulfill唤醒,返回x是对方节点的item(上一条解释了)。当自身取消后从awaitFulfill唤醒,返回x是自己节点的this。
  • e就是自身的item。就是当初创建节点用的item。
  • 既然,x代表交易对方的初始item,e就是自身的item。那么二者中肯定有一个是data node的初始item,那么返回其中的非null就好了。

互相帮忙式的CAS操作也有,这些操作只有一次会成功:

  • if (h == t || t.isData == isData)分支里的advanceTail(t, tn)advanceTail(t, s),这两个CAS都是帮忙把刚入队队尾但还没来得及更新tail的节点更新为tail。
  • advanceHead(t, s)advanceHead(h, m)。前者是,发现自己被正常匹配,然后更新head。后者是,发现head后继被正常匹配或取消,然后更新head。

不管是正常匹配还是取消自身,都不应该让item域再持有引用,以免内存泄漏。

  • 如果是自身节点取消,执行的是tryCancel(在awaitFulfill中执行的),item域指向自身,不管哪种节点(request or data),都释放了引用。
  • 如果是正常匹配,执行的是if (x != null) s.item = s,前面讲过x代表交易对方的初始item,说明交易对方是生产者,自身节点s是消费者,但由于正常匹配,自身节点s的item域已经从null变成非null,所以也需要释放引用。相反情况则不用执行。
        void advanceHead(QNode h, QNode nh) {if (h == head &&UNSAFE.compareAndSwapObject(this, headOffset, h, nh))h.next = h; // forget old next}

如果因为正常匹配而从队首出队时,过程是这样的:
在这里插入图片描述

awaitFulfill

  • 当因为正常匹配而返回时,此函数返回交易对方的初始item值(被唤醒之前,当前线程节点的item域就已经被修改为对方的item了)。
  • 当因为自身取消时,此函数返回当前线程节点this。
        Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {/* 整体思想和TransferStack.awaitFulfill一样,只注释不一样的地方 */final long deadline = timed ? System.nanoTime() + nanos : 0L;Thread w = Thread.currentThread();int spins = ((head.next == s) ?(timed ? maxTimedSpins : maxUntimedSpins) : 0);for (;;) {if (w.isInterrupted())s.tryCancel(e);//尝试把item域从初始的e变成thisObject x = s.item;if (x != e)  //不管是因为取消,还是因为正常匹配,这个都成立//如果是正常匹配,x肯定与e相反(这里指null 和 非null 相反)return x;if (timed) {nanos = deadline - System.nanoTime();if (nanos <= 0L) {s.tryCancel(e);continue;}}if (spins > 0)//这里不用再检查head.next == s了,因为这是队列结构,后来的节点肯定在s的后面--spins;else if (s.waiter == null)s.waiter = w;else if (!timed)LockSupport.park(this);else if (nanos > spinForTimeoutThreshold)LockSupport.parkNanos(this, nanos);}}

clean

此函数尝试从队列中移除s节点,但如果s是tail,此次调用函数不会执行移除动作,下次一定。

        void clean(QNode pred, QNode s) {s.waiter = null; // forget threadwhile (pred.next == s) { // Return early if already unlinkedQNode h = head;QNode hn = h.next;   // 从head开始吸收后面的取消节点if (hn != null && hn.isCancelled()) {advanceHead(h, hn);continue;}QNode t = tail;      // 在使用前才读取tail,尽量保证读到最新if (t == h)          // 如果head == tail,那么队列为空return;QNode tn = t.next;if (t != tail)       // 发现tail已经更新continue;if (tn != null) {    // tail没有更新,但tail有后继,这是个中间状态,帮忙更新tailadvanceTail(t, tn);continue;}if (s != t) {        // If not tail, try to unspliceQNode sn = s.next;// 如果s已经出队了(next指向自身)// 如果将pred -> s -> sn结构修改为 pred -> snif (sn == s || pred.casNext(s, sn))// 两种情况都说明s已经出队了return;}//执行到这里,说明s就是tail,这次调用函数不能删除s节点了,下次一定QNode dp = cleanMe;if (dp != null) {    // 发现cleanMe不为null,先移除cleanMe的后继QNode d = dp.next;  // d就是需要被移除的节点QNode dn;//结构为dp -> d -> dnif (d == null ||               // d已经被移除d == dp ||                 // d已经被出队!d.isCancelled() ||        // d应该是个取消节点的,但这里不是,说明已经被移除了(d != t &&                 // 接下来三行检查dp -> d -> dn的结构(dn = d.next) != null &&  dn != d &&                dp.casNext(d, dn)))       // 如果符合结构,那么尝试改成dp -> dn,即移除d节点casCleanMe(dp, null);      // 如果CAS成功,说明cleanMe后继移除成功//一般情况下,下一次循环将执行casCleanMe(null, pred),保存好cleanMe后离开if (dp == pred)  // 上面过程只是帮上一次调用擦屁股,这一次因为不能删除s节点,return;      // 所以也需要保存好cleanMe再离开函数。如果发现dp就是pred,那说明cleanMe已经保存好了} else if (casCleanMe(null, pred))  //如果cleanMe没被占领,占领它后直接returnreturn;          // 之后再调这个函数会删除cleanMe的后继的}}

函数比较好理解,主要在于当发现s节点为tail时,此次调用不会移除掉它,而是留到下一次调用此函数再清理。因为下一次调用s就不再是队尾tail了。

  • 当发现s是tail且cleanMe没被占领时,不执行移除动作,而是通过casCleanMe(null, pred)把需要移除节点的前驱暂时保存起来。
  • 当发现s是tail且cleanMe被占领时,先移除cleanMe的后继,然后再通过casCleanMe(null, pred)把需要移除节点的前驱暂时保存起来。

之所以不移除tail的原因–concurrency-interest

这个原因concurrency-interest上面的大佬已经给予我解释了,比如说,如果当前链表为A -> B -> C(tail) -> null,而且C节点需要被移除,那么链表应该变成A -> B(tail) -> null。那么现在有几个问题:

  1. 直接变成A -> B(tail) -> null,需要两个CAS操作,但这不可能是原子性的。
  2. 既然不可能是原子性,那么有的线程可能会看到A -> B -> nullC(tail) -> null。而这又会带来两个问题:
    1. 这破坏了只有一个节点的next为null的不变式。
    2. 别的线程可能会把新建节点加到C节点的后面。

总之,不移除tail可以避免很多问题,也可以说移除tail根本没法正确实现。

无效操作

SynchronousQueue完全没有容量的概念了,很多常用操作比如isEmpty()/size()/clear()/remove(Object)/contains(Object)/peek()都不支持了。可以用的公共方法只有:

  • 入队:put(E)/offer(E, long, TimeUnit)/offer(E)
  • 出队:take()/poll(long, TimeUnit)/poll()

总结

  • 同LinkedTransferQueue一样,队列中有两种节点data node和request node。
  • 不同于LinkedTransferQueue,SynchronousQueue除了队列的逻辑结构,还提供了栈的逻辑结构。
  • SynchronousQueue摒弃了与“线程交互通道”无关的操作。比如size()总是返回0。
  • 两种逻辑结构的transfer方法,在返回值上:如果匹配成功,则返回非null值;如果匹配失败或自身取消,则返回null值。
  • 栈结构的TransferStack,fulfill节点会加入到链表中;队列结构的TransferQueue,fulfill节点不会加入链表中,而是直接修改交易先到节点的item,这和LinkedTransferQueue一样。
http://www.lryc.cn/news/2417463.html

相关文章:

  • 任老爷子退休以后,华为谁最有可能接手,为什么?
  • 10款爆火且实用的AIGC工具大盘点
  • 许多代码段,没准儿有你需要的 C++ Builder
  • 智能安全的四大挑战:如何应对未来的风险
  • 变形金刚11280超清迅雷下载
  • drwtsn32.log 占用空间很大!
  • Android雪花效果 - 自定义View
  • Microsoft Academic Research
  • 开发过程中的版本
  • 使用web页面堆叠4台华为 S5720-52X-LI-AC交换机
  • 再学习《Java2核心技术》读书笔记(2)
  • 软件程序流程图使用规范
  • JSP中Servlet的使用(笔记)
  • MSN登陆不上解决方式
  • 华为鸿蒙第一批手机是哪一款,华为鸿蒙第一批名单
  • 华尔街英语学习软件_华尔街英语核心课程功能升级 让学员学习之旅更高效
  • 距离除夕倒计时 距离2022年除夕还有多少天用便签计算
  • 推荐的五款市面上常用的免费CMS建站系统
  • 台风路径1-通过python3.7.0获取台风路径历史轨迹点数据并插入到postgresql数据库中
  • 基于51单片机智能饭店餐厅点菜机器WiFi无线设计19-385
  • vs安装包哪里有mfc_VS 2008和2010的MFC功能包
  • bash破壳漏洞分析(二)
  • 织梦模板使用方法-安装教程
  • 信息安全系统和安全体系
  • kindle电子书和新闻推送及RSS资源…
  • pci-pcie总线序号分配
  • 安装Red Hat 8Linux操作系统
  • 突破MAC地址过滤连接网络
  • 如何使用W5300实现ADSL连接(一)
  • 基于AD7705的32路AD采集