ThreadPoolExecutor以BlockingQueue存储待执行任务,包括SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue,今天的目的是源码角度深入研究SynchronousQueue。

之后计划是继续研究LinkedBlockingQueue和ArrayBlockingQueue,搬开所有绊脚石之后再开始线程池。

基本概念#BlockingQueue

BlockingQueue是SynchronousQueue的爹,他们的祖先是Queue,所以他们都会遵从Queue的一些基本逻辑:比如按顺序存入数据、按顺序(FIFO或者LIFO)取出数据,都是从队首(head)获取数据,FIFO队列新数据从队尾入队、LIFO队列队列新数据入队首。

对于BlockingQueue,我们还是认真看一下他的javaDoc:

BlockingQueue是一个特殊的Queue:当获取数据的时候阻塞等待队列非空,存入数据的时候阻塞等待队列可用(有界队列未满)。
BlockingQueue的方法(获取数据或者存入数据)不能立即成功、但是将来某一时间点可能会成功的情况下,有四种不同的处理方式:一种是抛出异常,第二种是返回特殊值(空值或者false),第三种是无限期阻塞当前线程知道成功,第四种是阻塞等待设定的时间。
具体如下表:
Throws exception Special value Blocks Times out
Insert add add(e) offer offer(e) put put(e) offer(Object, long, TimeUnit) offer(e, time, unit)
Remove #remove remove() #poll poll() #take take() #poll(long, TimeUnit) poll(time, unit)
Examine #element element() #peek peek() not applicable not applicable
BlockingQueue不接受空值null,尝试写入null会抛出异常,因为BlockingQueue用null表示操作失败。
BlockingQueue可以是有界的也可以是无界的,有界队列维护剩余容量属性remainingCapacity,超出该属性后会阻塞写入操作。
无界队列没有容量限制,始终维护remainingCapacity为Integer.MAX_VALUE
BlockingQueue是线程安全的,BlockingQueue的实现类的方法都是原子操作、或者通过其他并发控制方式实现线程安全性。

基本概念SynchronousQueue

SynchronousQueue是一个每次写入数据都必须等待其他线程获取数据(反之亦然)的BlockingQueue。SynchronousQueue没有容量的概念、一条数据都不能存储。你不能对SynchronousQueue队列执行peek操作因为只有执行remove操作才能获取到数据,只有其他线程要remove数据的时候你才能插入数据,你也不能进行迭代因为他根本就没有数据。队列的队首数据就是第一个写入数据的线程尝试写入的数据,如果没有写入线程则队列中就没有数据可获取,poll()方法会返回null。对于集合类的其他方法、比如contains,SynchronousQueue的返回等同于空集合。
SynchronousQueue不允许空(null)元素。
通过构造参数设置fairness为true提供公平队列,公平队列遵从FIFO(先进先出)。

基本概念 Transferer

Transferer是SynchronousQueue的内部类,他的JavaDoc也很长:

Transferer扩展实现了W. N. Scherer III和M. L.Scott在"Nonblocking Concurrent Objects with Condition Synchronization"中描述的双栈(dual stack)或者双队列(dual queue)算法。
后进先出(Lifo)栈用来实现非公平模式,先进先出(Fifo)队列用来实现公平模式。两者的性能是一样的,一般情况下Fifo支持大吞吐量、Lifo maintains higher thread locality(抱歉,没搞懂什么意思)。
dual queue(或dual stack)在任一时间要么持有数据(写入操作提供的)、要么持有请求(获取数据的请求),向正好持有数据的队列请求数据、或者向持有请求的队列写入数据被称之为"fulfill"。最有趣的特性是对队列的任何操作都能知道队列当时处于什么状态,因此操作不必要上锁。
queue和stack都扩展自虚拟类Transferer,Transferer定义了一个方法transfer,可以同时实现put和take功能。把put和take统一在一个transfer方法中的原因是dual数据结构使得put和take操作是对称操作,所以两个方法的大部分代码都可以被合并。

好了,有关JavaDoc描述的特性就到这里了,SynchronousQueue的JavaDoc很不容易理解,代码也是。

开始分析源码:

  1. 构造函数:决定SynchronousQueue底层数据结构是用Queue还是Stack
  2. 由于SynchronousQueue是比较特殊的:不存储数据的(JavaDoc提到过),所以需要明确的是相关集合方法的返回也相应比较特殊,比如size=0,isEmpty=true等等,这部分源码就不看了,特别简单
  3. 队列存、取数据的方法,最终调用的都是Transferer的tranfer方法,所以我们主要就看这个方法

SynchronousQueue构造方法

提供一个有参构造方法接收一个布尔量fair,我们前面说过,Queue是公平的、Stack是非公平的,所以fair=true的话创建TransferQueue,否则创建TransferStack。

TransferStack#SNode

TransferStack(TransferQueue也一样)的源码虽然不多,但是必须首先了解清楚他的数据结构,否则不太容易读懂。

节点SNode:也就是存储到栈内的内容,注意我这里没有说存储在栈内的数据而是说内容,是因为TransferStack的特殊性导致说数据容易引起误解:栈内有两种类型的节点,一种是“data”,可以理解为“生产者”放到栈内等得消费者消费的数据,另一种是“request”,可以理解为消费者的消费请求,也就是说请求和数据都会入栈,都属于“节点”。

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;

next:下一节点。
match:当前节点的匹配节点,比如一个请求数据的Request节点入栈后,正好有一个data节点入栈,他们两个如果匹配成功的话,match就是对方节点。
waiter:如果当前节点即使在自旋等待后仍然没有被匹配,比如一个请求线程发送获取数据的请求后,该请求会以请求节点(Request节点)入栈,始终没有数据送进来,则当前节点的waiters就记录为当前线程,之后当前线程自己挂起,等待匹配。这个等待匹配的过程是被动的,只能被另外一个data线程送进来的data节点匹配,匹配之后data线程通过Request节点的waiters获取到其对应的线程后唤醒该线程。
item:data类的节点,记录送进来的待消费的数据,Request类的节点,item为null。
mode:当前节点的mode,有三个mode:data mode表示当前节点是数据节点(生产者发来的),Request mode表示当前节点是请求节点(消费者发来的),还有一个比较特殊的mode是:匹配中的数据节点或匹配中的请求节点,这个mode后面分析tranfer代码的时候再说。
head:头节点,栈结构嘛,入栈节点始终是头节点,也只有头节点具有正常出栈的权限。

SNode提供了几个原子性的操作:

  1. casNext:cas方式替换当前节点的下一节点
  2. tryCancel;这个实现比较特殊:当前节点的match如果为null的话则将match指向自己。用这种方式表示该节点被calcel
  3. casHead:cas的方式修改头节点,其实就是入栈或出栈操作

TransferStack#transfer

E transfer(E e, boolean timed, long nanos) {
      SNode s = null; // constructed/reused as needed
      //如果e为null的话就是REQUEST操作,否则就是DATA操作
      int mode = (e == null) ? REQUEST : DATA;
      for (;;) {
            //取头节点(首节点)h
           SNode h = head;
           //空栈,或者首节点mode与当前操作的mode相同,说明当前节点与首节点不可能匹配了
           if (h == null || h.mode == mode) {  // empty or same-mode
               //时间到,等不了了
               if (timed && nanos <= 0) {      // can't wait
                   //首节点被calcel了
                   if (h != null && h.isCancelled())
                       //首节点出栈
                       casHead(h, h.next);     // pop cancelled node
                   //既然等不及了,就返回null
                   else
                       return null;
               //否则,当前节点入栈,如果入栈成功,s就是首节点了
               } else if (casHead(h, s = snode(s, e, h, mode))) {
                   //调用awaitFulfill阻塞等待匹配节点
                   SNode m = awaitFulfill(s, timed, nanos);
                   //阻塞等待调用结果如果是s的话,说明s被取消了
                   if (m == s) {               // wait was cancelled
                       clean(s);
                       return null;
                   }
                   //否则就是阻塞等待后匹配成功了,那么判断如果头节点不空并且下一节点是s的话
                   //说明除了等来一个匹配节点之外,没有其他节点加入,那么这一对儿匹配节点都出栈
                   if ((h = head) != null && h.next == s)
                       casHead(h, s.next);     // help s's fulfiller
                   //成功匹配,可以返回了
                   return (E) ((mode == REQUEST) ? m.item : s.item);
               }
           //否则,存在头节点并且当前节点mode不同可以匹配,并且头节点尚未被其他线程匹配
           } else if (!isFulfilling(h.mode)) { // try to fulfill
               //如果头节点已经被取消,则出栈
               if (h.isCancelled())            // already cancelled
                   casHead(h, h.next);         // pop and retry
               //否则当前节点以FULLFILLING模式入栈,s变为首节点
               else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                   //一直循环直到成功匹配或栈内的等待节点突然消失
                   for (;;) { // loop until matched or waiters disappear
                       //m为s的下一节点
                       SNode m = s.next;       // m is s's match
                       //m空,说明以前的等待节点突然消失,比如等待节点超时取消
                       if (m == null) {        // all waiters are gone
                           //清空栈
                           casHead(s, null);   // pop fulfill node
                           //清空s,重新进入主循环
                           s = null;           // use new node next time
                           break;              // restart main loop
                       }
                       //否则,可以开始匹配了,mn为m的下一节点,为出栈做好准备
                       SNode mn = m.next;
                       //如果m和s能匹配成功,则m和s都出栈,返回结果
                       if (m.tryMatch(s)) {
                           casHead(s, mn);     // pop both s and m
                           return (E) ((mode == REQUEST) ? m.item : s.item);
                       //匹配没有成功,这种情况应该是m超时取消掉了,则m出栈
                       } else                  // lost match
                           s.casNext(m, mn);   // help unlink
                   }
               }
           //否则,栈首节点处于匹配中FULLFILLING的状态(其他线程正在匹配但是尚未完成)
           //这种情况下,新来的节点不入栈,先协助完成栈首节点的匹配
           } else {                            // help a fulfiller
                    SNode m = h.next;               // m is h's match
                    //首节点的下一节点为空(被取消了),则清空栈
                    if (m == null)                  // waiter is gone
                        casHead(h, null);           // pop fulfilling node
                    else {
                        //否则,去匹配首节点h和他的下一节点m,如果匹配成功了则h和m出栈
                        //这种情况下是不需要返回,因为是协助其他线程完成匹配,自己的匹配任务尚未开始呢...,其他线程如果获得执行权之后,会发现已经有人帮助他完成匹配了,所以会很快返回结果
                        SNode mn = m.next;
                        if (m.tryMatch(h))          // help match
                            casHead(h, mn);         // pop both h and m
                        else                        // lost match
                            h.casNext(m, mn);       // help unlink
                    }
                }
            }
        }

TransferStack#awaitFulfill

awaitFulfill的作用是通过自旋、或者阻塞当前线程来等待节点被匹配。

3个参数:
SNode s:等待匹配的节点。
booean timed:true则表示限时等待。
long nanos:限时等待时长。

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 (;;) {
                //如果当前线程被中断,则calcel掉当前节点:将s的match指向自己
                if (w.isInterrupted())
                    s.tryCancel();
                //自旋过程中完成匹配,则直接返回匹配节点
                SNode m = s.match;
                if (m != null)
                    return m;
                //如果是显示匹配并且匹配超时,则cancel掉s节点
                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)
                    s.waiter = w; // establish waiter so can park next iter
                //阻塞当前线程
                else if (!timed)
                    LockSupport.park(this);
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }

TransferStack#clean

clean方法在transfer方法中调用,如果当前节点在等待匹配的过程中已经被cancel掉的话。

代码不贴出了,基本逻辑就是首先清掉s及s关联的线程(s=null,s.waiter=null),然后清查并清理掉被cancel掉的head节点(从head到s之后的第一个未被cancel掉的节点逐个检查),直到确保栈的head节点正常(未被calcel)。

然后从head开始、到s之后的第一个未被cancel掉的节点逐个检查,如果有节点被标记为cancel则该节点出栈。

执行完成之后,不止是s节点被清理,栈内从head节点开始直到s节点的下一个未被cancel掉的节点之间的节点,如果被cancel掉的话,全部会被清理出栈。

小结

基于TransferStack的SynchronousQueue的源码就分析完成了,感觉不对照代码逐行说明的话,就很不容易说清楚TransferStack的transfer、awaitFulfill方法的代码逻辑,所以就采用在源码中逐行注释的方式来说明了。

篇幅原因,TransferQueue下次再说!

Thanks a lot!

上一篇 Runable和Callable的区别?你必须要搞清楚Thread以及FutureTask!
下一篇 BlockQueue - 基于TransferQueue的SynchronousQueue


45 声望17 粉丝