SynchronousQueue源码阅读

1.数据结构

1.等待队列:waitQueue

        waitQueue是一个接口,在这个队列当中会声明两个等待的队列,分别是生产者和消费者

	static class WaitQueue implements java.io.Serializable { }
	//生产者队列
    private WaitQueue waitingProducers;
    //消费者队列
    private WaitQueue waitingConsumers;

1.公平队列:FifoWaitQueue

        公平队列对应的是先进先出,对应的数据结构的queue

    static class FifoWaitQueue extends WaitQueue {
        private static final long serialVersionUID = -3623113410248163686L;
    }

2.非公平队列:LifoWaitQueue

        非公平队列对应的是后进先出,对应的数据结构是stack

    static class LifoWaitQueue extends WaitQueue {
        private static final long serialVersionUID = -3633113410248163686L;
    }

2.数据传输方式:Transferer

        数据传输方式,这边定义了一个抽象方法transfer,Performs a put or take.,是用来处理拿出和放入操作的.

    /**
     * Shared internal API for dual stacks and queues.
     */
    abstract static class Transferer<E> {
        /**
         * Performs a put or take.
         *
         * @param e if non-null, the item to be handed to a consumer;
         *          if null, requests that transfer return an item
         *          offered by producer.
         * @param timed if this operation should timeout
         * @param nanos the timeout, in nanoseconds
         * @return if non-null, the item provided or received; if null,
         *         the operation failed due to timeout or interrupt --
         *         the caller can distinguish which of these occurred
         *         by checking Thread.interrupted.
         */
        abstract E transfer(E e, boolean timed, long nanos);
    }

        这边分别对应着两种不同的传输方式:TransferStackTransferQueue

1.TransferStack

        TransferStack包含一个Snode节点,这边不直接看方法,看到队列操作之后再根据具体详细分析

        static final class SNode {
            volatile SNode next;        // next node in stack
            volatile SNode match;       // the node matched to this
            volatile Thread waiter;     // to control park/unpark
            Object item;                // data; or null for REQUESTs
            int mode;
       }

2.TransferQueue

        TransferQueue包含一个Qnode节点,这边不直接看方法,看到队列操作之后再根据具体详细分析

        TransferQueue() {
            QNode h = new QNode(null, false); // initialize to dummy node.
            head = h;
            tail = h;
        }

        static final class QNode {
            volatile QNode next;          // next node in queue
            volatile Object item;         // CAS'ed to or from null
            volatile Thread waiter;       // to control park/unpark
            final boolean isData;
        }

2.方法说明

1.转换(重点):transfer

        在这个类型队列的所有操作中基本都用到了transfer方法,主要的区别是是否计时,后面会讲到。这边只查看stack的实现:
在这里插入图片描述
        这一段代码比较长,这边我们分成四个部分来说,下面是代码的原貌。后面我们一个一个部分来理解.

 E transfer(E e, boolean timed, long nanos) {
            // constructed/reused as needed
            SNode s = null;
            //当e==null的时候说明是消费者进来等待了(REQUEST),否则表示生产者(DATA)生产了等待消费
            int mode = (e == null) ? REQUEST : DATA;

            for (; ; ) {
                //拿到头结点
                SNode h = head;
                // empty or same-mode
                //如果头结点为空,或者也是相同的模式(比如都是消费者)
                if (h == null || h.mode == mode) {
                    // can't wait
                    //时间到了
                    if (timed && nanos <= 0) {
                        // 如果头节点不为空且是取消状态
                        if (h != null && h.isCancelled()) {
                            // pop cancelled node
                            //把头结点弹出
                            casHead(h, h.next);
                        } else {
                            return null;
                        }
                    }
                    //cas会进行入栈,这边是入栈成功了
                    else if (casHead(h, s = snode(s, e, h, mode))) {
                        //睡眠,如果是超时或者是中断,会把match替换成this(自己)尝试取消
                        SNode m = awaitFulfill(s, timed, nanos);
                        //返回节点的match是自己,取消
                        if (m == s) {
                            // wait was cancelled
                            //清除当前节点
                            clean(s);
                            return null;
                        }
                        //匹配到元素:因为从awaitFulfill()里面出来要不被取消了要不就匹配到了
                        //如果头结点不为空,并且s是头结点下一个节点,弹出head和s.
                        if ((h = head) != null && h.next == s) {
                            // help s's fulfiller
                            casHead(h, s.next);
                        }
                        //根据模式返回生产者还是消费者的值
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    }
                    // try to fulfill
                }
                //如果h.mode不是FULFILLING,并且不是相同的模式
                else if (!isFulfilling(h.mode)) {
                    // already cancelled
                    //节点取消
                    if (h.isCancelled()) {
                        // pop and retry
                        //头结点换成下一个节点
                        casHead(h, h.next);
                    }
                    //这边应该是头结点 不是cancelled的,s入栈变成头结点
                    else if (casHead(h, s = snode(s, e, h, FULFILLING | mode))) {
                        // loop until matched or waiters disappear
                        for (; ; ) {
                            // m is s's match
                            SNode m = s.next;
                            // all waiters are gone
                            //如果没有下一个节点了,清空栈跳出循环继续往下走
                            if (m == null) {
                                // pop fulfill node
                                casHead(s, null);
                                // use new node next time
                                s = null;
                                // restart main loop
                                break;
                            }
                            //如果头结点的下一个节点不为空,往再下一个节点找
                            SNode mn = m.next;
                            //这个地方的条件是是两个可以匹配的节点(生产和消费)
                            if (m.tryMatch(s)) {
                                //匹配成功,两个节点都弹出
                                // pop both s and m
                                casHead(s, mn);
                                return (E) ((mode == REQUEST) ? m.item : s.item);
                                // lost match
                            } else {
                                //尝试匹配失败,说明m已经先一步被其它线程匹配了,清除m节点
                                // help unlink
                                s.casNext(m, mn);
                            }
                        }
                    }
                    // help a fulfiller
                }
                //如果不是相同模式,并且当前节点是FULFILLING的情况
                else {
                    //mode在上面只可能是生产或者消费,这边的话说明都是FULFILLING的情况
                    // m is h's match
                    SNode m = h.next;
                    // waiter is gone
                    if (m == null) {
                        //头结点的下一个节点为null
                        // pop fulfilling node
                        casHead(h, null);
                    } else {
                        //否则尝试匹配.
                        SNode mn = m.next;
                        // help match
                        //匹配成功,两个节点都弹出
                        if (m.tryMatch(h)) {
                            // pop both h and m
                            casHead(h, mn);
                        }
                        // lost match
                        else {
                            // help unlink
                            //尝试匹配失败,说明m已经先一步被其它线程匹配了,清除m节点
                            h.casNext(m, mn);
                        }
                    }
                }
            }
        }

1.区分操作模式:mode

        这边说的模式是相对于线程操作来说的,这边定义的三种模式:REQUEST(消费)、DATA(生产)、FULFILLING(交换),第一部分是怎么判断这个模式:int mode = (e == null) ? REQUEST : DATA;
        这边的话,根据入参,如果传入的是null就是要消费,可以看下面的take方法,如果传入的不是null,就是要生产,可以看下面的put,这个应该很好理解。

2.头节点和当前是相同模式

        这边的操作步骤分别如下:
        先判断是否超时,如果超时的话,判断头部节点不为空,并且被取消(匹配的节点是自己,也就是说没有匹配到可以进行交换的节点),通过cas方式把头部换成下一个节点。如果头部节点为空, 说明没有队列了,直接返回null;
        如果没有超时一说,先把当前节点添加到栈中,这边的栈的入栈和以前的入栈可能不一样(后入的节点添加到head,然后出栈的时候出的也是head,实现后进先出),然后下一步调用awaitFulfill方法,这个方法的作用是找到匹配的节点(不同模式),如果没找到自旋结束挂起等待被唤醒,match如果是自己则说明匹配失败(被cancel)
        往后判断一下匹配的节点是不是自己,如果是则清除当前节点返回null。否则就是匹配到了节点(因为awaitFulfill方法只有一个出口,出口必定携带匹配的节点值),匹配到之后设置头部节点,根据mode返回相应的节点值(item)

		//时间到了
         if (timed && nanos <= 0) {
             // 如果头节点不为空且是取消状态
             if (h != null && h.isCancelled()) {
                 // pop cancelled node
                 //把头结点弹出
                 casHead(h, h.next);
             } else {
                 return null;
             }
         }
         //cas会进行入栈,这边是入栈成功了
         else if (casHead(h, s = snode(s, e, h, mode))) {
             //睡眠,如果是超时或者是中断,会把match替换成this(自己)尝试取消
             SNode m = awaitFulfill(s, timed, nanos);
             //返回节点的match是自己,取消
             if (m == s) {
                 // wait was cancelled
                 //清除当前节点
                 clean(s);
                 return null;
             }
             //匹配到元素:因为从awaitFulfill()里面出来要不被取消了要不就匹配到了
             //如果头结点不为空,并且s是头结点下一个节点,弹出head和s.
             if ((h = head) != null && h.next == s) {
                 // help s's fulfiller
                 casHead(h, s.next);
             }
             //根据模式返回生产者还是消费者的值
             return (E) ((mode == REQUEST) ? m.item : s.item);
         }
         // try to fulfill

        看一下awaitFulfill方法:
        这边先是会计算一下循环的次数
        然后进入死循环,出口只有一个就是 match!=null的时候,返回match值。
        如果被中断和超时的情况下会调用trycancel方法会将当前节点的match值设置为自己。然后下一个循环开始的时候,match不为null就返回了
        如果是没有计时的情况,可以看到park的操作,这边的park是等待下一个操作进来的时候去唤醒这个节点。我们可以往后去看下面的方法

 SNode awaitFulfill(SNode s, boolean timed, long nanos) {
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Thread w = Thread.currentThread();
            //计算循环的次数,这边会分别根据有计时和没计时进行判断
            int spins = (shouldSpin(s) ? (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            for (; ; ) {
                //被中断,尝试取消这个节点
                if (w.isInterrupted()){
                    // Tries to cancel a wait by matching node to itself.
                    //1 - 尝试通过将节点与自身匹配来取消等待。
                    s.tryCancel();
                }
                //匹配的节点不为空返回
                SNode m = s.match;
                if (m != null){
                    return m;
                }
                //有计时的操作,到达时间取消节点
                if (timed) {
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        //2 - 尝试通过将节点与自身匹配来取消等待。
                        s.tryCancel();
                        continue;
                    }
                }

                //还有自旋次数,计算自旋次数
                if (spins > 0){
                    spins = shouldSpin(s) ? (spins - 1) : 0;
                }
                //没有自旋次数,设置waiter为当前线程
                else if (s.waiter == null){
                    // establish waiter so can park next iter
                    s.waiter = w;
                }
                //没有自旋次数并且不计数,进行park休眠
                else if (!timed){
                    LockSupport.park(this);
                }
                //如果有计时,则休眠对应时间
                else if (nanos > spinForTimeoutThreshold){
                    LockSupport.parkNanos(this, nanos);
                }
            }
        }

3.头结点和当前当前不是相同模式

        如果头节点和当前节点不是相同的模式,这边还有一个条件就是不是辅助模式。也就是说可能进来的节点是消费(take),而头结点是生产(put),那么这两个模式不一样,正好可以抵消了。所以就进入这个判断:
        这边会先判断节点是否被取消(就是匹配的等于自己,这个是操作基本是中断和超时导致),头结点被取消就往下一个节点走
否则把当前节点添加到头部(入栈),并且修改状态为交换,开始死循环
        如果没有下一个节点,设置头结点为null,跳出循环结束,下一步就会再走到上面的2.头节点和当前是相同模式,可能被挂起等待或者超时结束。
        如果下一个节点不为空的情况,进行tryMatch匹配,如果能匹配到出栈(这个时候是两个节点),返回对应的元素
        如果匹配不上,说明可能被另外一个节点匹配了,就要往在下一个节点去匹配。

           // already cancelled
            //节点取消
            if (h.isCancelled()) {
                // pop and retry
                //头结点换成下一个节点
                casHead(h, h.next);
            }
            //这边应该是头结点 不是cancelled的,s入栈变成头结点
            else if (casHead(h, s = snode(s, e, h, FULFILLING | mode))) {
                // loop until matched or waiters disappear
                for (; ; ) {
                    // m is s's match
                    SNode m = s.next;
                    // all waiters are gone
                    //如果没有下一个节点了,清空栈跳出循环继续往下走
                    if (m == null) {
                        // pop fulfill node
                        casHead(s, null);
                        // use new node next time
                        s = null;
                        // restart main loop
                        break;
                    }
                    //如果头结点的下一个节点不为空,往再下一个节点找
                    SNode mn = m.next;
                    //这个地方的条件是是两个可以匹配的节点(生产和消费)
                    if (m.tryMatch(s)) {
                        //匹配成功,两个节点都弹出
                        // pop both s and m
                        casHead(s, mn);
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                        // lost match
                    } else {
                        //尝试匹配失败,说明m已经先一步被其它线程匹配了,清除m节点
                        // help unlink
                        s.casNext(m, mn);
                    }
                }
            }
            // help a fulfiller
        }

        tryMatch如何进行匹配,这边看一下
        这边如果匹配成功,成功的前提原来的头结点(当前节点的下一个节点)的match为空(没有被匹配),并且cas操作是从null到当前节点成功,可能失败,就是在这个过程中被其他节点匹配走了,如果成功要唤醒原来的头结点,因为之前可能被park起来等待,现在match不为空了,可以起来工作了,匹配失败返回结果

            boolean tryMatch(SNode s) {
                //如果m没有匹配这,将s作为他的匹配这
                if (match == null && UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
                    Thread w = waiter;
                    // waiters need at most one unpark
                    //唤醒m中的waiter
                    if (w != null) {
                        waiter = null;
                        LockSupport.unpark(w);
                    }
                    // 返回true
                    return true;
                }
                //可能有其他线程匹配了m,返回是否匹配结果
                return match == s;
            }

4.当前节点是辅助模式(可能从上一步来)

        因为之前的第二个判断会把当前节点设值为交换中状态。这边的操作相当于第二个判断之后会进来
        操作和上面的操作很类似,如果head的下一个节点为null,也要回到第一个判断在进行操作等待。
        否则尝试匹配,成功弹出两个节点,失败继续往下,不过这边成功没有返回的打算,依然会再进入自旋的下一步操作
        这边可以理解这一步操作是辅助操作,清除匹配成功的节点,或者当节点所属线程消失后将其移除栈。

//mode在上面只可能是生产或者消费,这边的话说明都是FULFILLING的情况
                    // m is h's match
                    SNode m = h.next;
                    // waiter is gone
                    if (m == null) {
                        //头结点的下一个节点为null
                        // pop fulfilling node
                        casHead(h, null);
                    } else {
                        //否则尝试匹配.
                        SNode mn = m.next;
                        // help match
                        //匹配成功,两个节点都弹出
                        if (m.tryMatch(h)) {
                            // pop both h and m
                            casHead(h, mn);
                        }
                        // lost match
                        else {
                            // help unlink
                            //尝试匹配失败,说明m已经先一步被其它线程匹配了,清除m节点
                            h.casNext(m, mn);
                        }
                    }

5.图解

在这里插入图片描述

2. 获取一个元素:take

    public E take() throws InterruptedException {
        //直接调用transfer方法,不计数
        E e = transferer.transfer(null, false, 0);
        if (e != null){
            return e;
        }
        //如果元素为空,让线程中断抛出中断异常结束.
        Thread.interrupted();
        throw new InterruptedException();
    }

        可以看到take这边是直接传入null,表明自己是消费者,并且没有设置超时时间,一直等待到对应的生产者给定数据返回

3.加入队列:put

        put这边也不设定时间,相当于是等待消费者消费掉当前操作生产的节点.

    public void put(E e) throws InterruptedException {
        if (e == null) {
            throw new NullPointerException();
        }
        //把当前节点添加进去执行transfer操作
        if (transferer.transfer(e, false, 0) == null) {
            //返回为空就中断被排除异常
            Thread.interrupted();
            throw new InterruptedException();
        }
    }

3.总结

        可以看到虽然说这个队列内部的容量是0,但是他还是会维护一个队列,个人感觉这种队列适合用于短期的任务,生产和消费相近,不会造成大量任务堆积。等后面看完线程池再回来补充吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

了-凡

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值