Java多線程 -- JUC包源碼分析12 -- ThreadPoolExecutor源碼分析__線程池

來源:互聯網
上載者:User

在JUC包中,線程池部分本身有很多組件,可以說是前面所分析的各種技術的一個綜合應用。從本文開始,將綜合前面的知識,逐個分析線程池的各個組件。
-Executor/Executors
-ThreadPoolExecutor使用介紹
-ThreadPoolExecutor實現原理
ThreadPoolExecutor的中斷與優雅關閉 shutdown + awaitTermination
shutdown的一個誤區 Executor/Executors

Executor是線程池架構最基本的幾個介面:

public interface Executor {    void execute(Runnable command);}

而Executors是線程池架構的一個工具類,利用它可以方便的建立不同策略的線程池:

//單線程線程池:corePoolSize = maxPoolSize = 1, 隊列用的LinkedBlockingQueue    public static ExecutorService newSingleThreadExecutor() {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new   LinkedBlockingQueue<Runnable>()));    }
//固定數目的線程池:corePoolSize = maxPoolSize = n, 隊列用的LinkedBlockingQueue    public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }
//1。CachedThreadPool,corePoolSize = 0,  隊列為SynchronousQueue,maxPoolSize = Integer.MAX_VALUE(這也就意味著,每來一個任務,就建立一個線程。//2。關於SynchronousQueue,後面會單獨用一篇來分析。它是個特殊的隊列,沒本身沒有容量,放進去一個,就得等有線程拿出來,才能解除阻塞//3。從構造參數可以看出,空閑線程,60s沒人用,回收    public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }
//單線程的,具有周期調度功能的線程池    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {        return new DelegatedScheduledExecutorService            (new ScheduledThreadPoolExecutor(1));    }//多線程的,具有周期調度功能的線程池    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {        return new ScheduledThreadPoolExecutor(corePoolSize);    }    public ScheduledThreadPoolExecutor(int corePoolSize) {        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,              new DelayedWorkQueue());    }

從上面可以看出,Executors的各個工具函數,都用的ThreadPoolExecutor/ScheduledThreadPoolExecutor這2個類,下面做詳細分析。 ThreadPoolExecutor ThreadPoolExecutor建構函式詳解

下面是ThreadPoolExecutor的參數最全的建構函式,搞清楚了每個參數的含義,也就明白了線程池的各種不同策略,也就明白了上述Executors工具類中的各個工具函數。

    public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) {        if (corePoolSize < 0 ||            maximumPoolSize <= 0 ||            maximumPoolSize < corePoolSize ||            keepAliveTime < 0)            throw new IllegalArgumentException();        if (workQueue == null || threadFactory == null || handler == null)            throw new NullPointerException();        this.corePoolSize = corePoolSize;        this.maximumPoolSize = maximumPoolSize;        this.workQueue = workQueue;        this.keepAliveTime = unit.toNanos(keepAliveTime);        this.threadFactory = threadFactory;        this.handler = handler;    }

corePoolSize: 線程池始終維護的線程個數
maxPoolSize: corePooSize滿了,隊列也滿的情況下,擴充線程至這個值
keepAliveTime/TimeUnit: maxPoolSize中的空閑線程,過多長時間銷毀,匯流排程數收縮回corePoolSize
blockingQueue: 線程池所用的隊列類型
threadFactory: 線程建立工廠,可以自訂,也有一個預設的
RejectedExecutionHandler: corePoolSize滿了,隊列滿了,maxPoolSize滿了,最後的拒絕策略。 ThreadPool任務處理流程

從上述建構函式解釋,可以看出每次submit的任務,有如下的處理流程:
step1: 判斷當前線程數 >= corePoolSize。如果小於,建立線程執行;如果大於,進入step2
step2: 判斷隊列是否已滿。未滿,放入;已滿,進入step3
step3: 判斷當前線程數 >= maxPoolSize。如果小於,建立線程執行;如果大於,進入step4
step4: 根據拒絕策略,拒絕任務

總結一下:先判斷corePoolSize, 再判斷blockingQueue,再判斷maxPoolSize,最後使用拒絕策略 ThreadPool的4中拒絕策略

ThreadPoolExecutor的4個內部類,分別定義了4種策略。預設是AbortPolicy

//策略1:讓調用者直接在自己的線程裡面執行,線程池不做處理    public static class CallerRunsPolicy implements RejectedExecutionHandler {        public CallerRunsPolicy() { }        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {            if (!e.isShutdown()) {                r.run();            }        }    }//策略2:線程池直接拋異常    public static class AbortPolicy implements RejectedExecutionHandler {        public AbortPolicy() { }        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {            throw new RejectedExecutionException("Task " + r.toString() +                                                 " rejected from " +                                                 e.toString());        }    }//策略3:線程池直接把任務丟掉,當作什麼也沒發生    public static class DiscardPolicy implements RejectedExecutionHandler {        public DiscardPolicy() { }        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {        }    }//策略4:把隊列裡面最老的任務刪除掉,把該任務放入隊列    public static class DiscardOldestPolicy implements RejectedExecutionHandler {        public DiscardOldestPolicy() { }        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {            if (!e.isShutdown()) {                e.getQueue().poll();                e.execute(r);            }        }    }
ThreadPoolExecutor實現原理

一般都知道,ThreadPool的基本實現原理就是一個隊列 + 一組worker線程,調用中不斷往隊列中放,worker線程不斷去取。但在具體實現中,有不同的實現策略:
策略1: 阻塞隊列 vs. 非阻塞隊列
在ThreadPoolExecutor中,使用的是阻塞隊列,即如下的BlockingQueue介面:

private final BlockingQueue<Runnable> workQueue;

這也就意味著,worker內部不需要自己設定wait/notify機制,它只管從隊列中取,取的到執行,取不到,自動會阻塞。

也有使用非阻塞隊列的,比如Tomcat 6裡面的線程池實現(以後會源碼詳細分析),當沒有請求處理時,worker內部自己實現阻塞,然後又新的請求進來,再通知woker。

策略2:新來的請求,是直接放入隊列,還是先new一個新的thread。
ThreadPool的處理方式是優先new thread處理,thread count >= corePoolSize的時候,再考慮放入隊列。

策略3: 無界隊列 vs. 有界隊列。
如果無界隊列,意味著maxPoolSize的邏輯永遠不會執行。這在上面的Executors中,FixedThreadPool已有所體現。

除此之外,還有諸多實現上的細節,下面代碼詳細分析 源碼分析

//核心結構:一個BlockingQueue + 一個線程的Set +  一把鎖(控制對workers, 各種threadCount的互斥訪問)public class ThreadPoolExecutor extends AbstractExecutorService {。。。    private final BlockingQueue<Runnable> workQueue;      private final ReentrantLock mainLock = new ReentrantLock();    private final HashSet<Worker> workers = new HashSet<Worker>();。。。}
    public void execute(Runnable command) {        if (command == null)            throw new NullPointerException();        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {   //小於corePoolSize的判斷            if (runState == RUNNING && workQueue.offer(command)) { //入隊列                if (runState != RUNNING || poolSize == 0)                    ensureQueuedTaskHandled(command); //進入隊列之後,2次檢測            }            else if (!addIfUnderMaximumPoolSize(command))  //小於maxPoolSize的判斷                reject(command); // 大於maxPoolSize,拒絕請求        }    }    //poolSize < corePoolSize的時候,直接new Thread,加入hashSet    private boolean addIfUnderCorePoolSize(Runnable firstTask) {        Thread t = null;        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            if (poolSize < corePoolSize && runState == RUNNING)                t = addThread(firstTask);        } finally {            mainLock.unlock();        }        return t != null;    }    //隊列滿了,poolSize < maxPoolSize,再次new thread,加入hashSet    private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {        Thread t = null;        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            if (poolSize < maximumPoolSize && runState == RUNNING)                t = addThread(firstTask);        } finally {            mainLock.unlock();        }        return t != null;    }

Worker的實現

    private final class Worker implements Runnable {    。。。    private Runnable firstTask; //至所以有firstTask這個變數,是因為建立worker的時候,可以直接賦給它一個task執行;有可以不賦給task,讓它自己到blockingQueue裡面去迴圈取    Worker(Runnable firstTask) {            this.firstTask = firstTask;        }    //1個死迴圈,不斷從blockingQueue中,取task執行。取不到,就會阻塞在getTask()裡面    public void run() {            try {                hasRun = true;                Runnable task = firstTask;                 firstTask = null;                while (task != null || (task = getTask()) != null) {                    runTask(task);                    task = null;                }            } finally {                workerDone(this);  //worker線程退出            }        }   。。。   }//getTask裡面有個關鍵點:當poolSize <= corePoolSize時,是無限期阻塞下去,線程也就會一直存在,不會退出,死掉;當poolSize > corePoolSize或者允許coreThread也死去時,線程就只阻塞keepAliveTime的時間,時間到了,隊列還是空的,沒有請求,線程就退出,死掉了,同時poolSize--.    Runnable getTask() {        for (;;) {            try {                int state = runState;                if (state > SHUTDOWN)                    return null;                Runnable r;                if (state == SHUTDOWN)                      r = workQueue.poll();  //poll是非阻塞調用,沒有直接返回null                else if (poolSize > corePoolSize || allowCoreThreadTimeOut)                    r = workQueue.poll(keepAliveTime,  TimeUnit.NANOSECONDS);  //等待1個逾時時間,預設就是建構函式裡面傳進去的那個60s                else                    r = workQueue.take();  //take是阻塞調用,沒有,一直阻塞                if (r != null)                    return r;                if (workerCanExit()) {                    if (runState >= SHUTDOWN)                         interruptIdleWorkers();                    return null;                }            } catch (InterruptedException ie) {                // On interruption, re-check runState            }        }    }
中斷與優雅關閉 線程池狀態切換圖

    volatile int runState;    static final int RUNNING    = 0;    static final int SHUTDOWN   = 1;    static final int STOP       = 2;    static final int TERMINATED = 3;

初始處於RUNNING狀態,當調用shutdown()之後,切換到SHUTDOWN狀態;調用shutdownNow(),切換到STOP狀態。

那shutdown與shutdownNow有什麼區別嗎。
shutdown(): 不會清空隊列裡面的任務,會等所有任務執行完畢。並且它只會中斷那些 > corePoolSize的idle線程

shutdownNow(): 清空隊列裡面所有任務,同時向所有線程發送中斷訊號

當隊列為空白 && pool也為空白時,線程池進入Terminated狀態。 shutdown/shutdownNow源碼解析

 public void shutdown() {    SecurityManager security = System.getSecurityManager();    if (security != null)            security.checkPermission(shutdownPerm);  //許可權檢查,check當前調用者,是否有許可權關閉線程池。沒有許可權,拋出異常。        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            if (security != null) {                 for (Worker w : workers)                    security.checkAccess(w.thread);  //許可權檢查            }            int state = runState;            if (state < SHUTDOWN)                runState = SHUTDOWN;   //從running切換到shutdown。不能從stop或者terminated切換到shutdown            try {                for (Worker w : workers) {                    w.interruptIfIdle();  //遍曆所有線程,向其發送訊號                }            } catch (SecurityException se) {                 runState = state;                throw se;            }            tryTerminate(); //試圖終止線程池        } finally {            mainLock.unlock();        }    }    public List<Runnable> shutdownNow() {    SecurityManager security = System.getSecurityManager();    if (security != null)            security.checkPermission(shutdownPerm);        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        try {            if (security != null) {                for (Worker w : workers)                    security.checkAccess(w.thread);            }            int state = runState;            if (state < STOP)                runState = STOP;    //切換到stop狀態            try {                for (Worker w : workers) {                    w.interruptNow();  //變數所有線程,發中斷訊號,不管是否正在執行任務                }            } catch (SecurityException se) { // Try to back out                runState = state;                // tryTerminate() here would be a no-op                throw se;            }            List<Runnable> tasks = drainQueue();  //清空隊列請求            tryTerminate(); // 試圖終止線程池             return tasks;        } finally {            mainLock.unlock();        }    }

從上面,可以看出,shutdown和shutdownNow的區別有3點:
(1)一個是切換到shutdown狀態,一個是切換到stop狀態
(2)遍曆所有線程,一個調用的interruptIfIdle, 一個調用的interruptNow。
(3)shutdownNow會清空隊列中的任務

那interruptIfIdle和interruptNow有什麼區別呢。

    private final class Worker implements Runnable {        。。。        private final ReentrantLock runLock = new ReentrantLock();        void interruptIfIdle() {            final ReentrantLock runLock = this.runLock;            if (runLock.tryLock()) {                try {                    if (hasRun && thread != Thread.currentThread())                        thread.interrupt();                } finally {                    runLock.unlock();                }            }        }        void interruptNow() {            if (hasRun)                thread.interrupt();        }        public void run() {            try {                hasRun = true;                Runnable task = firstTask;                firstTask = null;                while (task != null || (task = getTask()) != null) { //getTask內部,也有響應中斷的邏輯                    runTask(task);                    task = null;                }            } finally {                workerDone(this);            }        }        //每次從隊列中拿出一個任務,執行之前,會加鎖         private void runTask(Runnable task) {            final ReentrantLock runLock = this.runLock;            runLock.lock();            try {                if ((runState >= STOP ||                    (Thread.interrupted() && runState >= STOP)) &&                    hasRun)                    thread.interrupt();                boolean ran = false;                beforeExecute(thread, task);                try {                    task.run();                    ran = true;                    afterExecute(task, null);                    ++completedTasks;                } catch (RuntimeException ex) {                    if (!ran)                        afterExecute(task, ex);                    throw ex;                }            } finally {                runLock.unlock();            }        }

可以看出,interruptIfIdle和interuptNow的關鍵區別是:前者會加鎖訪問,這也就意味著,如果被中斷的線程,正在執行runTask,則鎖是拿不到的。此時shutdown會阻塞,直到woker執行完runTask。 shutdown的一個誤區

根據上面分析,是不是shutdown一定會阻塞到隊列中所有請求都執行完,再返回呢。或者說,shutdown返回的時候,是不是隊列裡面的請求就一定執行完了呢。

不一定。shutdown返回之後,線程池不一定立即關閉。為什麼呢。

請看下面的getTask函數

    Runnable getTask() {        for (;;) {            try {                int state = runState;                if (state > SHUTDOWN)                    return null;                Runnable r;                if (state == SHUTDOWN)  //如果線程池是shutdown狀態,就不阻塞了,不管是否能拿到,都是直接返回                    r = workQueue.poll();  //關鍵點:如果是shutdown狀態,會一直迴圈,直到拿空隊列裡面所有任務                else if (poolSize > corePoolSize || allowCoreThreadTimeOut)                    r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);                else                    r = workQueue.take();  //case1: 別的線程先置了中斷標誌位,然後當前線程調用take  //case 2:  先調用take阻塞在這,然後別的線程置了中斷標誌位  //2種case,都會拋出異常,進入下面的InterruptedException                if (r != null)                    return r;                if (workerCanExit()) {                    if (runState >= SHUTDOWN) // Wake up others                        interruptIdleWorkers();                    return null;                }                // Else retry            } catch (InterruptedException ie) {   //阻塞的時候,收到中斷,不處理,再次迴圈檢查                // On interruption, re-check runState            }        }    }        public void run() {            try {                hasRun = true;                Runnable task = firstTask;                firstTask = null;                while (task != null || (task = getTask()) != null) { //getTask內部,也有響應中斷的邏輯                    runTask(task);                    task = null;                }            } finally {                workerDone(this);            }        }        //每次從隊列中拿出一個任務,執行之前,會加鎖         private void runTask(Runnable task) {            final ReentrantLock runLock = this.runLock;            runLock.lock();            try {                if ((runState >= STOP ||                    (Thread.interrupted() && runState >= STOP)) &&                    hasRun)                    thread.interrupt();                boolean ran = false;                beforeExecute(thread, task);                try {                    task.run();                    ran = true;                    afterExecute(task, null);                    ++completedTasks;                } 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.