• 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的排队方式是,将厨师拍成一条长队,顾客们你们随便来,来一个顾客我就把排最前面的厨师挑选出来,给你服务
  • 相关阅读:
    Kali linux installation instruction
    Source Insight(C/C++/Java编辑器)用法
    Wear OS软件安装指南
    外卖ERP管理系统(一)
    邮轮ERP系统
    使用CEfSharp 下载文件 弹出保存框 IDownloadHandler
    cefSharp通过js操控页面,含跨域操控
    C# Winform 未能加载文件或程序集"System.Data.SQLite"或它的某一个依赖项。试图加载格式不正确的程序
    无法将类型为“System.__ComObject”的 COM 对象强制转换为类类型“mshtml.HTMLInputElementClass
    COMBOBOX绑定DICTIONARY做为数据源
  • 原文地址:https://www.cnblogs.com/lcmlyj/p/16375326.html
Copyright © 2020-2023  润新知