阻塞队列之SynchronousQueue

SynchronousQueue是一个特殊的阻塞队列,生产者插入操作后只能等待消费者移除操作,它没有任何容量,甚至没有存储一个元素的容量。在SynchronousQueue中不能使用peek方法,因为元素只有被移除时才存在,只有消费者移除了元素生产者才能往队列中插入元素,当然更不能进行迭代。队列的头节点是第一个排队插入元素的线程,队列是不允许存储null的元素。生产者和消费者必须互相等待,这样经过一个元素的生产到消费的过程,然后一致离开。

SynchronousQueue有两种策略,一种是公平模式,使用队列来完成,一种是非公平模式,使用栈来完成,不论是栈或队列都是用链表来实现的 。看一下SynchronousQueue的创建:

SynchronousQueue<String> s = new SynchronousQueue<>();
SynchronousQueue<String> b = new SynchronousQueue<>(true);

接下来看一下它的构造方法

    /**
     * 默认无参构造
     */
    public SynchronousQueue() {
        this(false);
    }

    /**
     * 指定是否使用公平模式
     */
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }

默认是创建一个非公平模式,也可以实现公平模式,如果是公平模式的话,可以保证第一个队首的线程是等待时间最长的线程,这时可以把SynchronousQueue看作是一个FIFO队列。无论是队列或栈,它们都继承了Transferer类。

基本属性

   /** CPU的数量 */
    static final int NCPUS = Runtime.getRuntime().availableProcessors();

    static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;

    static final int maxUntimedSpins = maxTimedSpins * 16;

    static final long spinForTimeoutThreshold = 1000L;

这四个属性是用来设置自旋时间的。阻塞是一个非常消耗性能的操作,要进行线程之间上下文的切换,所以一般在阻塞之前进行自旋,不停的使用死循环进行检测,当然不能永远停在循环中,必须要设定时间限定,如果在时间限定内通过自旋完成了某种操作,那就不用阻塞提高了响应速度。如果超时要进行阻塞。

当然在竞争激烈的情况下,自旋的时间长点也是可以的,但是过长的自旋也会白白浪费CPU的时间,所以时间限定并没有一个准确的值,要根据不同的情况进行设定。

首先是获取CPU的数量,分两种情况,一种是设定了时间限的自旋,如果CPU的数量是1,那就不进行自旋,只有一个CPU在进行自旋就不能进行其他操作了,CPU的数量>=2,设定maxTimedSpins为32。另一种是没有设定时间限的自旋,如果CPU的数量>=2,把maxUntimeSpins设为32*16,如果CPU的数量为1,把maxUntimeSpins设为0。

spinForTimeoutThreshold是为了防止自定义的时间过长而设置的,单位是纳秒,如果设定的时间>这个值,那就把spinForTimeoutThreshold当作时间限。

 private transient volatile Transferer<E> transferer;
SynchronousQueue内有两个内部类TransferQueue和TransferStack,它们都继承了Transfer类,transferer就是具体实现的引用,所有方法都要基于transferer去执行。


TransferStack

        /* Modes for SNodes, ORed together in node fields */
        /** Node represents an unfulfilled consumer */
        static final int REQUEST    = 0;
        /** Node represents an unfulfilled producer */
        static final int DATA       = 1;
        /** Node is fulfilling another unfulfilled DATA or REQUEST */
        static final int FULFILLING = 2;
TransferStack的三种状态,REQUEST表示消费者,DATA表示生产者,FULFILLING表示匹配另一个生产者或者消费者。任何线程对TransferStack的操作都应该是这三种状态中的一种。

看一下它的内部类snode。

       /** Node class for TransferStacks. */
        static final class SNode {
            volatile SNode next;        // 栈中的下一个节点
            volatile SNode match;       // 相匹配的节点
            volatile Thread waiter;     // 当前节点代表的线程
            Object item;                // 具体的item值或者null
            int mode;                   // 代表当前线程的模式
            // item和mode字段不需要设置成volatile类型的,因为它们总是在写入之前,读操作之后

            SNode(Object item) {  // snode的构造方法
                this.item = item;
            }
        }
         // 创建一个snode节点
        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类的方法。

casNext就是设置当前节点的下一个节点。

tryCancel就是取消当前操作。

isCancelled就是把当前的match匹配变成this,如果当前节点的match是空说明当前节点的任务还没有完成,换成this匹配自己说明任务被取消。

重点是tryMatch方法。

            boolean tryMatch(SNode s) {
                if (match == null &&
                    UNSAFE.compareAndSwapObject(this, matchOffset, null,
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值