Java线程池之可缓存的线程池Executors.newCachedThreadPool()与阻塞队列SynchronousQueue

前言:上一篇单线程池的随笔提到了非固定线程数的线程池,那么本文就以前文为基础,剖析Java线程池中的Executors.newCachedThreadPool()Executors.newCachedThreadPool(),后文简称为缓存线程池

Executors.newCachedThreadPool()源码解析

先看构建缓存线程池的源码

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

缓存线程池执行的时候,参数可以以此解释为:核心线程数0,最大线程数为Integer最大值,存活时间为60秒,然后以SynchronousQueue作为队列使用。

ExecutorService.execute的执行过程

在上一文中,ExecutorService.execute(command)的主要代码为:

int c = ctl.get();
//1.
if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
        return;
    c = ctl.get();
}
//2.
if (isRunning(c) && workQueue.offer(command)) {
    int recheck = ctl.get();
    if (! isRunning(recheck) && remove(command))
        reject(command);
    else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
}
//3.
else if (!addWorker(command, false))
    reject(command);

根据debug断点,描述一个execute的执行过程:

  1. 提交第一个任务,第一步,肯定是返回false;
  2. 第二步,workQueue.offer(command)直接返回false
  3. 第三步,然后执行!addWorker(command, false),关于此逻辑不再赘述,上一文中已分析,而对于缓存线程池addWorker最核心的的是,Worker执行第一个Runnable任务,待执行完毕,然后执行while循环,去getTask()
  4. 执行到getTask()中的workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)逻辑,会根据keepAliveTime阻塞;
  5. 最关键的来了,这个时候,提交第二个Runnable任务,在workQueue.offer(command)的时候返回了true,与此同时,第一个任务的WorkerworkQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)获取到了第二个任务的command

上面的execute执行过程画个简易的流程图


此外,还进行了补充:如果workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)超时,那么此Node节点会被清除,Worker工作线程也会被杀掉。


那么,这个SynchronousQueue阻塞队列究竟是怎么工作的呢,下面一节做详细介绍。

核心组件SynchronousQueue解析

进入new SynchronousQueue对象的源码可以看见:

public SynchronousQueue() {
    this(false);
}

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

默认的SynchronousQueue对象,fairfalse,见名知意非公平,这一下子让我联想到可重入锁ReentrantLock的非公平锁,随后,根据fairfalse最终使用TransferStack作为transferer的默认对象。

SynchronousQueue默认数据结构TransferStack栈

看到栈这种数据结构,它的特点就是先进后出。那么我们先看构造方法的源码:

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    return transferer.transfer(e, true, 0) != null;
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    E e = transferer.transfer(null, true, unit.toNanos(timeout));
    if (e != null || !Thread.interrupted())
        return e;
    throw new InterruptedException();
}

可以发现,offerpoll方法最终都是执行的transferer.transfer方法。只不过参数上有差异,offere对象是有值的,而polle是空的。


那么,TransferStacktransfer()方法里在做什么呢,下面看源码:

E transfer(E e, boolean timed, long nanos) {
    SNode s = null; // constructed/reused as needed 
    int mode = (e == null) ? REQUEST : DATA;
    for (;;) {
        SNode h = head;
        if (h == null || h.mode == mode) {  // empty or same-mode
            if (timed && nanos <= 0) {      // can't wait
                if (h != null && h.isCancelled())
                    casHead(h, h.next);     // pop cancelled node
                else
                    return null; // [注释1]
            } else if (casHead(h, s = snode(s, e, h, mode))) {
                SNode m = awaitFulfill(s, timed, nanos);  // [注释2]
                if (m == s) {               // wait was cancelled
                    clean(s);
                    return null;
                }
                if ((h = head) != null && h.next == s)
                    casHead(h, s.next);     // help s's fulfiller
                return (E) ((mode == REQUEST) ? m.item : s.item);
            }
        } else if (!isFulfilling(h.mode)) { // try to fulfill // [注释3]
            if (h.isCancelled())            // already cancelled
                casHead(h, h.next);         // pop and retry
            else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                for (;;) { // loop until matched or waiters disappear
                    SNode m = s.next;       // m is s's match
                    if (m == null) {        // all waiters are gone
                        casHead(s, null);   // pop fulfill node
                        s = null;           // use new node next time
                        break;              // restart main loop
                    }
                    SNode mn = m.next;
                    if (m.tryMatch(s)) {
                        casHead(s, mn);     // pop both s and m
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    } else                  // lost match
                        s.casNext(m, mn);   // help unlink
                }
            }
        } 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 {
                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
            }
        }
    }
}
  • 提交第一个任务,执行offer,走入注释1的逻辑,返回null并最终返回false
  • 第一个任务在Worker执行完毕,执行getTask(),执行poll,最终走入逻辑注释2中的awaitFulfill方法,并阻塞住。下面看看awaitFulfill方法的逻辑:
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())
            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)
            s.waiter = w; // establish waiter so can park next iter
        else if (!timed)
            LockSupport.park(this);
        else if (nanos > spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanos);
    }
}
  • 可以发现,在循环了一定次数之后(根据spins判断),如果还没有match的节点,那么执行LockSupport.parkNanos(this, nanos)进行线程的等待,这里涉及并发编程的其它内容,后面再详解;
  • 此时,线程池提交了第二个任务,然后执行到了offer,此时会进入注释3中的逻辑,执行m.tryMatch(s)并成功:
boolean tryMatch(SNode s) {
    if (match == null &&
        UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
        Thread w = waiter;
        if (w != null) {    // waiters need at most one unpark
            waiter = null;
            LockSupport.unpark(w);
        }
        return true;
    }
    return match == s;
}
  • m.tryMatch(s)中,给阻塞的head节点的match变量成功赋值,并唤醒了waiter线程,就是之前的等待线程LockSupport.parkNanos(this, nanos)

至此,缓存线程池的逻辑完成了闭环:首次执行任务1,没有可用线程,则新增Worker线程并启动,Runnable任务执行完毕,执行poll方法加入队列并阻塞,阻塞超时则踢出队列并关闭线程。如果此时遇到线程池提交Runnable任务2,执行offer方法时,线程任务1的poll可以感知,任务2便可在任务1的线程中执行。文字加上上面的流程图便更好理解了。
那么,SynchronousQueue的数据结构除了默认的TransferStack,还有队列TransferQueue

SynchronousQueue数据结构TransferQueue队列

上面已经比较详细介绍栈了,下面先看看队列(FIFO,先进先出)的transfer方法的源码,然后我进行一个简单的流程介绍,就不做过多的描述了:

E transfer(E e, boolean timed, long nanos) {
    QNode s = null; // constructed/reused as needed
    boolean isData = (e != null);
    for (;;) {
        QNode t = tail;
        QNode h = head;
        if (t == null || h == null)         // saw uninitialized value
            continue;                       // spin
        if (h == t || t.isData == isData) { // empty or same-mode [注释1]
            QNode tn = t.next;
            if (t != tail)                  // inconsistent read
                continue;
            if (tn != null) {               // lagging tail
                advanceTail(t, tn);
                continue;
            }
            if (timed && nanos <= 0)        // can't wait
                return null;
            if (s == null)
                s = new QNode(e, isData);
            if (!t.casNext(null, s))        // failed to link in
                continue;
            advanceTail(t, s);              // swing tail and wait
            Object x = awaitFulfill(s, e, timed, nanos);
            if (x == s) {                   // wait was cancelled
                clean(t, s);
                return null;
            }
            if (!s.isOffList()) {           // not already unlinked
                advanceHead(t, s);          // unlink if head
                if (x != null)              // and forget fields
                    s.item = s;
                s.waiter = null;
            }
            return (x != null) ? (E)x : e;
        } else {                            // complementary-mode [注释2]
            QNode m = h.next;               // node to fulfill
            if (t != tail || m == null || h != head)
                continue;                   // inconsistent read
            Object x = m.item;
            if (isData == (x != null) ||    // m already fulfilled
                x == m ||                   // m cancelled
                !m.casItem(x, e)) {         // lost CAS
                advanceHead(h, m);          // dequeue and retry
                continue;
            }
            advanceHead(h, m);              // successfully fulfilled
            LockSupport.unpark(m.waiter);
            return (x != null) ? (E)x : e;
        }
    }
}
  • 首先在执行完第一次任务1之后,getTask()并执行poll,进入注释1awaitFulfill逻辑:
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
    /* Same idea as 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);
        Object x = s.item;
        if (x != e)
            return x;
        if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                s.tryCancel(e);
                continue;
            }
        }
        if (spins > 0)
            --spins;
        else if (s.waiter == null)
            s.waiter = w;
        else if (!timed)
            LockSupport.park(this);
        else if (nanos > spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanos);
    }
}
  • 等有其它任务2被线程池提交时,则进入注释2的逻辑,将任务放入队列的前方,并唤醒任务1的线程,然后任务1的线程执行任务2.

SynchronousQueueLinkedBlockingQueue对比

LinkedBlockingQueue SynchronousQueue.TransferStack SynchronousQueue.TransferQueue
数据结构 链表,先进先出(任务commad) 栈,先进后出(Worker线程) 链表,先进先出(Worker线程)
执行offer Runnable放入队列,直接返回true 如果是首次执行,返回false。如果有其它线程执行了poll在等待,那么会将Runnablepoll线程,并返回true 同SynchronousQueue .TransferStack
执行poll 从队列拿出Runnable 将当前线程放入队列中,等待其它任务提交offer 同SynchronousQueue .TransferStack

关于数据结构的解释:

  • 上面的LinkedBlockingQueue的先进后出的什么意思是:任务来了之后,会将任务放入队列,实行先进先出的原则进行执行,先进入队列的任务优先执行;
  • SynchronousQueue .TransferQueue队列的先进先出的意思是:Worker创建好并执行完任务之后,将Worker线程放入队列,等待其它任务的提交,那么其它任务提交时,先进入队列的Worker线程会被优先选择,用于执行任务;
  • 打个比方
    • LinkedBlockingQueue中,将Worker比作厨师Runnable顾客LinkedBlockingQueue的排队方式是,顾客排成一条长队,厨师们争先恐后地抢着为顾客服务,不过也要遵守规矩,依次从排在最前面的顾客挑选出来进行服务;
    • SynchronousQueue中,同样将Worker比作厨师Runnable顾客,但是SynchronousQueue的排队方式是,将厨师拍成一条长队,顾客们你们随便来,来一个顾客我就把排最前面的厨师挑选出来,给你服务

posted on 2022-06-14 22:42  lyjlyjlyj  阅读(2318)  评论(0编辑  收藏  举报

导航