Java源码解读系列9—SynchronousQueue(公平模式,JDK1.8 )

1 概述

  • SynchronousQueue(简称SQ)是BlockingQueue接口的实现类,是一种阻塞、线程安全的、不存储任何元素的同步队列。SQ的底层实现分为两种,一种是非公平模式(默认),采用栈的数据结构,先入队的数据,后出队,即可LIFO;另外一种是公平模式,采用队列的数据结构,先入队的数据,先出队,即FIFO。

  • SQ跟LinkedBlockingQueue(LBQ)和ArrayBlockingQueue(ABQ)等阻塞队列,最大一个不同点是一个线程t1执行put操作后,必须有另外线程t2执行take操作,t1才能继续添加,否则t1会被阻塞。从这个角度去理解,才认为SQ本身不存储数据。在性能方面,由于SQ不存储数据,吞吐量高于LBQ和ABQ

  • 本文主要讲解SQ公平模式实现方法。

2 用例

使用SynchronousQueue队列可以快速构建一个生产者消费者的模型,因为生产者线程执行添加操作后会发生阻塞,除非有消费者线程取出才能会被唤醒。反之,如果消费者消费不到数据也会发生阻塞,直至有生产者执行添加操作。

public class SynchronousQueueApp {
    //同步队列容器
    private static SynchronousQueue queue = new SynchronousQueue(true);

    /**
     * 生产方法
     */
    private static void product(){
        try {
            String content = System.currentTimeMillis() + "";
            queue.put(content);
            String tName = Thread.currentThread().getName();
            System.out.println(tName + "生产了" + content);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    /**
     * 消费方法
     */
    private static void consumer(){
        try {
            Object content = queue.take();
            String tName = Thread.currentThread().getName();
            System.out.println(tName + "消费了" + content);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception{
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,
                10,
                0,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());
                
        Thread productThread = new Thread(new Runnable() {
            @Override
            public void run() {
                 product();

            }
        });

        Thread consumerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                    consumer();
            }
        });

        executor.execute(productThread);
        executor.execute(consumerThread);
        executor.shutdown();
    }
}

控制台打印结果:

pool-1-thread-1生产了1617297399290
pool-1-thread-2消费了1617297399290

3 构造函数

无参构造函数默认使用非公平模式,即SynchronousQueue类的实现方法通过TransferStack;
公平模式需使用有参构造函数,且入参指定为true,SynchronousQueue类的实现方法为TransferQueue。

 public SynchronousQueue() {
        this(false);
    }
    
public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }

TransferQueue类是SynchronousQueue的内部类。在TransferQueue的构造函数中,会new一个新的QNode,并将head和tail引用指向同一个QNode。

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

4 存储数据节点QNode

QNode是Transferer类的内部节点

 static final class QNode {
    //指向下一个节点
    volatile QNode next;          // next node in queue
    //数据项,若item存储的数据为节点本身,说明该节点被取消了
    volatile Object item;         // CAS'ed to or from null
    //存储当前线程
    volatile Thread waiter;       // to control park/unpark
    //模式判断,true为添加操作,false为取出操作
    final boolean isData;

    //有参构造函数
    QNode(Object item, boolean isData) {
        this.item = item;
        this.isData = isData;
    }

    //通过CAS操作更新下一个节点
    boolean casNext(QNode cmp, QNode val) {
        return next == cmp &&
            UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

     //通过CAS操作更新当前节点的数据项
    boolean casItem(Object cmp, Object val) {
        return item == cmp &&
            UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }

    /**
     * Tries to cancel by CAS'ing ref to this as item.
     */
     //尝试取消当前节点
     //实现原理是通过CAS操作将当前节点的数据项指向当前对象
    void tryCancel(Object cmp) {
        UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
    }

   //判断当前节点是否被取消
    boolean isCancelled() { 
        //判断节点的数据项是否指向当前对象,若是则被取消
        return item == this;
    }

    /**
     * Returns true if this node is known to be off the queue
     * because its next pointer has been forgotten due to
     * an advanceHead operation.
     */
     //判断该节点是否出队
    boolean isOffList() {
        return next == this;
    }

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long itemOffset;
    private static final long nextOffset;

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = QNode.class;
            itemOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("item"));
            nextOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("next"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

5 图解节点的入队和出队

SQ的代码相对LBQ和ABQ复杂多了,在深入源码之前,建议先看下添加和取出的过程图。

第一步大家都一样,t1执行put操作,head和tail引用初始化,head和tail同时指向QNode1,此时item为空
在这里插入图片描述

5.1 put-take

executor.execute(productThread);
Thread.sleep(2000);
executor.execute(consumerThread);

1)t1将QNode1的next指向新增的QNode2,QNode2为队尾,tail引用指向QNode2。
此时因为没有其他线程执行take操作,t1就自旋一段时间后阻塞了
在这里插入图片描述

2)t2执行take操作,通过CAS将QNode2的item置为空,head和tail同时指向QNode,释放QNode1,唤醒t1,最后返回数据“data1”

在这里插入图片描述

5.2 take-put

executor.execute(consumerThread);
Thread.sleep(2000);
executor.execute(productThread);

1)t1将QNode1的next指向新增的QNode2,QNode2为队尾,tail引用指向QNode2。
此时因为没有其他线程执行put操作,t1就自旋一段时间后阻塞了
在这里插入图片描述
2)t2将QNode2的item更新为“data1”
在这里插入图片描述
3)t2让QNode1出队,h引用指向Onode2,唤醒t1
在这里插入图片描述
4)t1醒来后,将QNode2的item引用更新为QNode2本身,返回数据“data1”
在这里插入图片描述
5)看到这里你可能有疑问,那QNode2的item项目不为空了啊?没啥,如果后面又有新的线程成功执行完take操作,会让QNode2出队,通过GC将QNode2回收。

5.3 put-put-take-take

executor.execute(productThread);
Thread.sleep(2000);
executor.execute(productThread);
Thread.sleep(2000);
executor.execute(consumerThread);
Thread.sleep(2000);
executor.execute(consumerThread);

1)线程t2第二次put的时候,由于尾节点QNode2的模式和Qnode3的模式一样,都属于数据节点,因此QNode3会被添加到队列尾部

在这里插入图片描述

2)第一次执行take线程,会将QNode1出队,并将Qnode2得item置为空,唤醒线程t1

在这里插入图片描述

3)第二次执行take线程,会将QNode2出队,并将Qnode3得item置为空,唤醒线程t2

在这里插入图片描述

6 阻塞添加操作(put方法)

  public void put(E e) throws InterruptedException {
        //非空校验
        if (e == null) throw new NullPointerException();
        //和take方法类似,都是调用transferer.transfer方法,唯一不同的是第一个入参e非空
        if (transferer.transfer(e, false, 0) == null) {
            Thread.interrupted();
            throw new InterruptedException();
        }
    }

7 阻塞式取出操作(take方法)

 public E take() throws InterruptedException {
         //和put方法类似,都是调用transferer.transfer方法,唯一不同的是第一个入参为null
        E e = transferer.transfer(null, false, 0);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }

8 取出和添加的公共操作

8.1transfer方法

 E transfer(E e, boolean timed, long nanos) {
            //待插入队列节点
            QNode s = null; // constructed/reused as needed
            //put操作调用transfer,e不为null,isData为true
            //take操作调用transfer,e为null,isData为false
            boolean isData = (e != null);

            //无限自旋
            for (;;) {
                //将尾节点赋予t
                QNode t = tail;
                //将头节点赋予t
                QNode h = head;
                
                //t或者h为空,重新自旋
                if (t == null || h == null)         // saw uninitialized value
                    continue;                       // spin

               //1)当h==t,即队列为空  2)待插入节点模式与尾节点的模式相同
                if (h == t || t.isData == isData) { // empty or same-mode
                     //获取t节点的下一个节点
                    QNode tn = t.next;
                    //t不等于tail,说明被其他线程改动过
                    if (t != tail)                  // inconsistent read
                        continue;
                   / /tn不为空,说明被其他线程改动过
                    if (tn != null) {               // lagging tail
                        //通过CAS,将tail节点的引用指向tn
                        advanceTail(t, tn);
                        //重新自旋
                        continue;
                    }
                    //超时处理
                    if (timed && nanos <= 0)        // can't wait
                        return null;
                    //s为空,new一个节点并赋予s
                    if (s == null)
                        s = new QNode(e, isData);
                     //将s添加到尾巴节点的位置
                    if (!t.casNext(null, s))        // failed to link in
                        //重新自旋
                        continue;
                    //通过CAS,将tail节点的引用指向s
                    advanceTail(t, s);              // swing tail and wait
                    //等待直到有另外一个线程执行相反的操作,put对应take,反之亦然
                    Object x = awaitFulfill(s, e, timed, nanos);
                    //x等于s,说明s节点被取消了
                    if (x == s) {                   // wait was cancelled                  
                       //从队头开始移除被取消的结点
                        clean(t, s);
                        return null;
                    }                    
                    //判断当前节点是否出被取消
                    if (!s.isOffList()) {           // not already unlinked
                       //通过CAS将head点指向s
                        advanceHead(t, s);          // unlink if head
                        //如果x不为空
                        if (x != null)              // and forget fields
                           //is节点的itme指向自身
                           //说明item节点被取消了
                            s.item = s;
                        //将s节点中存储的等待线程清空
                        s.waiter = null;
                    }
                    //若x不为空,返回(E)x,否则返回e
                    return (x != null) ? (E)x : e;
                    
                //待插入节点模式与尾节点的模式不同相同
                } else {                            // complementary-mode
                     //m为头节点的下一个节点
                    QNode m = h.next;               // node to fulfill
                    //如果1)t不等于tail 2)m为空 3)h不等于头节点,说明已经有其他线程发生改变
                    if (t != tail || m == null || h != head)
                        //重新自旋
                        continue;                   // inconsistent read
                      
                     //x为m中的数据项
                    Object x = m.item;                    
                    //1)x != null为true,说明x不等于空,m是数据节点。
                         如果isData等于true,说明待插入节点和头节点的类型一样,说明已经有其他线程发生改变
                    2)x == m,说明m节点被取消了
                    3)m.casItem(x, e)将m的item替换为e
                    if (isData == (x != null) ||    // m already fulfilled
                        x == m ||                   // m cancelled
                        !m.casItem(x, e)) {         // lost CAS
                        //h节点出队
                        advanceHead(h, m);          // dequeue and retry
                        //重新自旋
                        continue;
                    }
                    
                    //通过CAS,将head的引用指向m
                    advanceHead(h, m);              // successfully fulfilled
                    //唤醒m中等阻塞的线程
                    LockSupport.unpark(m.waiter);
                    //x不为空,返回 (E)x,否则返回e
                    return (x != null) ? (E)x : e;
                }
            }
        }

8.2 awaitFulfill方法

等待直到有另外一个线程成功执行匹配的操作,put对应take,反之亦然

        Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
            /* Same idea as TransferStack.awaitFulfill */
             //超时时间,time为false时,deadline为0 
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            //获取当前线程
            Thread w = Thread.currentThread();
            
            //spins为自旋次数
            //判断s是否头节点,若是则需要自旋。这样设计有个好处,公平模式是先进先出,如果s前面还要其他节点,就没必要尝试自旋了,因为s不是第一个出队的元素
            int spins = ((head.next == s) ?
                         (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            
            //无限循环
             //自旋
            for (;;) {
               //判断当前线程是否被中断
                if (w.isInterrupted())
                    //取消当前节点,即可将当前节点的item指向自己
                    s.tryCancel(e);
                Object x = s.item;
                
                //s节点中的item存储的元素不等于传入的e,,说明已经有另外一个线程成执行了匹配操作
                if (x != e)
                    return x;
                 //超时处理
                if (timed) {
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        s.tryCancel(e);
                        continue;
                    }
                }
                //自旋次数大于0
                if (spins > 0)
                    // 执行自旋次数减1操作
                    --spins;
                 //s中的等待线程为空
                else if (s.waiter == null)
                    //将waiter指向前线程
                    s.waiter = w;
                 //spins 为0,且没有设置超时时间
                else if (!timed)
                    //阻塞当前线程
                    //如果有另外一个线程匹配成功,可以通过s节点的waiter可以找到阻塞的线程并将其唤醒
                    LockSupport.park(this);
                 //如果nanos小于spinForTimeoutThreshold,因为1000纳秒的超时等待无法做到十分的精确,而且非常断,干脆不发生阻塞
                else if (nanos > spinForTimeoutThreshold)
                    //阻塞指定超时时间
                    LockSupport.parkNanos(this, nanos);
            }
        }

8.3 clean方法

从头开始移除被取消节点

//如果被取消的节点s是尾节点,clean是s的前驱节点,cleanMe的next引用指向s
transient volatile QNode cleanMe;


void clean(QNode pred, QNode s) {
    //将s节点中包含的阻塞线程清空
    s.waiter = null; // forget thread
   
        /*无论什么时候,如果被取消的节点s是最后插入的节点,它是不能被删除。因此,我们使用cleanMe这个引用去保存s的前驱节点;
        *后面清理时,如果发现s不是尾节点,就可以是用s的前驱节点的下一个节点指向s的下一个节点,从而释放s
        */
    while (pred.next == s) { // Return early if already unlinked
        //将头节点赋值给h
        QNode h = head;
        //头节点的下一个节点
        QNode hn = h.next;   // Absorb cancelled first node as head
        
        //如果hn不为空且hn被取消
        if (hn != null && hn.isCancelled()) {
            //通过CAS将head指向hn
            advanceHead(h, hn);
            //重新自旋
            continue;
        }
        //将尾节点赋值给t
        QNode t = tail;      // Ensure consistent read for tail
        //h==t,说明队列为空
        if (t == h)
           //结束clean方法
            return;
         //t的下一个节点
        QNode tn = t.next;
        //t不等于tail,说明队列被他线程改变
        if (t != tail)
           //重新自旋
            continue;
         //tn 不为空,说明队列被他线程改变
        if (tn != null) {
             //通过CAS将tail指向tn
            advanceTail(t, tn);
            //重新自旋
            continue;
        }
        
        //s不为尾节点,说明队列被他线程改变
        if (s != t) {        // If not tail, try to unsplice
             //s的下一个节点
            QNode sn = s.next;
            //1)sn等于s 或 2)通过CAS释放sn成功
            if (sn == s || pred.casNext(s, sn))
                //结束clean方法
                return;
        }
        QNode dp = cleanMe;
        
        //移除被取消的节点
        if (dp != null) {    // Try unlinking previous cancelled node
            //d是cleanMe的下一个节点
            QNode d = dp.next;
            //临时节点
            QNode dn;
           
           //d为空
            if (d == null ||               // d is gone or
               //d已经出队,dp的next指向自己
                d == dp ||                 // d is off list or
                //d没有被取消
                !d.isCancelled() ||        // d not cancelled or
                //d!=t说明d不是尾节点
                (d != t &&                 // d not tail and
                //dn是d的下一个节点且不为空
                 (dn = d.next) != null &&  //   has successor
                 //d没有出队
                 dn != d &&                //   that is on list
                 //t通过CAS将d的下一个节点指向d的下下个节点,说白了就说将d的下一个节点移除
                 dp.casNext(d, dn)))       // d unspliced
                 //通过CAS将cleanMe置为空
                casCleanMe(dp, null);
                
             //dp == pred,说明s是最后一个插入的节点
            if (dp == pred)
               //结束clean方法
                return;      // s is already saved node
       //cleanMe为空,说明s是最后一个插入的节点
        } else if (casCleanMe(null, pred))
            //结束clean方法
            return;          // Postpone cleaning s
    }
}

9 参考文献

1)JDK7在线文档
https://tool.oschina.net/apidocs/apidoc?api=jdk_7u4
2) JDK8在线文档
https://docs.oracle.com/javase/8/docs/api/
3) Bruce Eckel. Java编程思想,第4版,2007,机械工业出版社
4)方腾飞,魏鹏,程晓明. Java并发编程的艺术,第1版,2015年,机械工业出版社

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值