怎麼纔算掌握了JDK中的線程池

 

怎麼纔算掌握了JDK中的線程池

JDK併發包下面的線程池是面試中常常被考查的點,以前我寫過一篇ThreadPoolExecutor源碼分析的文章。由於篇幅有限當時沒說面試中常見的考查點和哪些點是應該掌握。那篇文章着實有點長,更合適用電腦看,結合源碼看。今天,我來談談本身以爲ThreadPoolExecutor哪些點是應該掌握的,這些點應該掌握的點正是面試中常常被問的東西。如今拋出幾個問題,若是你都能答上來,能夠不用往下面看啦。java

  1. ThreadPoolExecutor中經常使用參數有哪些,做用是什麼?任務提交後,ThreadPoolExecutor會按照什麼策略去建立線程用於執行提交任務?
  2. ThreadPoolExecutor有哪些狀態,狀態之間流轉是什麼樣子的?
  3. ThreadPoolExecutor中的線程哪一個時間點被建立?是任務提交後嗎?能夠在任務提交前建立嗎?
  4. ThreadPoolExecutor中建立的線程哪一個時間被啓動?
  5. ThreadPoolExecutor居然是線程池那麼他是如何作到重複利用線程的?
  6. ThreadPoolExecutor中建立的同一個線程同一時刻能執行多個任務嗎?若是不能是經過什麼機制保證ThreadPoolExecutor中的同一個線程只能執行完一個任務,纔會機會去執行另外一個任務?
  7. ThreadPoolExecutor中關閒線程池的方法shutdown與shutdownNow的區別是什麼?
  8. 經過submit方法向ThreadPoolExecutor提交任務後,當全部的任務都執行完後不調用shutdown或shutdownNow方法會有問題嗎?
  9. ThreadPoolExecutor有沒有提供擴展點,方便在任務執行前或執行後作一些事情?

若是回答的上就pass吧,哈哈面試

ThreadPoolExecutor參數有哪些與建立線程策略?

ThreadPoolExecutor參數

  • corePoolSize 線程池中的核心線程數
  • mmaximumPoolSize 線程池中的最大線程數
  • keepAliveTime 當線程池中線程數量超過corePoolSize時,容許等待多長時間從workQueue中拿任務
  • unit keepAliveTime 對應的時間單位,爲TimeUnit類。
  • workQueue 阻塞隊列,當線程池中線程數超過corePoolSize時,用於存儲提交的任務。
  • threadFactory 線程池採用,該線程工廠建立線程池中的線程。
  • handler 爲RejectedExecutionHandler,當線程線中線程超過maximumPoolSize時採用的,拒絕執行處理器。

建立線程策略

 

image.png

 

簡單介紹一下,一個任務提交給線程池後,線程池建立線程來執行提交任務的流程。
一、當提交任務時線程池中的來用執行任務的線程數小於corePoolSize(核心線程數),則線程池利用ThreadFacory(線程工廠)建立線程用於執行提交的任務。不然執行第二2步。
二、當提交任務時線程池中的來用執行任務的線程數大於corePoolSize(核心線程數),但workQueue沒有滿,則線程池會將提交的任務先保存在workQueue(工做隊列),等待線程池中的線程執行完其它已提交任務後會循環從workQueue中取出任務執行。不然執行第3步。
三、當提交任務時線程池中的來用執行任務大於corePoolSize(核心線程數),且workQueu已滿,但沒有超過maximunPoolSize(最大線程數),則線程池利用ThreadFacory(線程工廠)建立線程用於執行提交的任務。不然執行4。
四、當提交任務時線程池中的來用執行任務大於maximunPoolSize,執行線程池中配置的拒絕策略(RejectedExecutionHanlder)。
因此在設置ThreadPoolExecutor的參數時必定要特別當心,不建議採用很大的ArrayBlockQueue或不限大小的LinkedBlockQueue,同時corePoolSize也不該該設置過大。CUP密集的任務的話能夠設置小一點(CUP數據+1這種)避免沒必要要的上下文切換;而對於IO密集的任務則corePoolSize則能夠設置的大一點,能夠避免長時間IO等待而CUP卻空閒。threadFactory建議採用本身定義的,讓其建立的線程容易區分,方便問題定位。併發

線程池有哪些狀態,狀態之間流轉是什麼樣子的?

  • RUNNING:運行中,接收新的任務或處理隊列中的任務。
  • SHUTDOWN:關閉,再也不接收新的任務,但會處理隊列中的任務值爲0。
  • STOP:中止,再也不接收新的任務,也不處理隊列中的任務,並中斷正在處理的任務。
  • TIDYING:全部任務已結束隊列大小爲0,轉變TIDYING狀態的線程將會執行terminated()方法。
  • TERMINATED:結束terminated()已被執行完。
    狀態流程以下圖:

 

image.png

 

池程池中的線程哪一個時間點被建立?

ThreadPoolExecutor中的線程哪一個時間點被建立?是任務提交後嗎?能夠在任務提交前建立嗎?
通常在任務被提交後,線程池會利用線程工廠去建立線程,但當線程池中線程數已爲corePoolSize時或maxmumPoolSize時不會。能夠在任務提交前經過prestartCoreThread方法或prestartAllCoreThreads方法預先建立核心線程。具體能夠參考這下這個圖:ide

 

image.png

 

ThreadPoolExecutor中建立的線程哪一個時間被啓動?

線程池中線程實現是在addWorker方法中被建立的,詳見以前文章中addWorker方法分析。建立後完,該線程就被啓動。線程池中被建立的線程被封裝到了Worker對象中,而Worker類又實現了Runnable接口,線程池中的線程又引用了worker。當線程被start後實際就有機會等待操做系統調度執行Worker類的run方法。源碼分析

Worker(Runnable firstTask) {
  setState(-1); 
  this.firstTask = firstTask;
 //建立的線程引用了worker
  this.thread = getThreadFactory().newThread(this);
}

 

ThreadPoolExecutor居然是線程池那麼他是如何作到重複利用線程的?

一旦線程池經過ThreadFactory建立好線程後,就會將建立的線程封裝到了Worker對象中,同時啓動該線程。新建立的線程會執行剛提交的任務,同時會不斷地從workerQueue中取出任務執行。線程池的線程複用正是經過不斷地從workerQueue中取出任務來執行達到的。源碼分析見runWorkers方法分析。this

ThreadPoolExecutor中建立的同一個線程同一時刻能執行多個任務嗎?

同時一時刻不能執行多個任務,只有一個任務執行完時才能去執行另外一個任務。上面說到線程池中經過ThreadFacory建立的線程最後會被封裝到Worker中,而該線程又引用了Worker,start線程後,任務實際上是在Worker中的run方法中被執行,最終run又將任務執行代理給ThreadPoolExecutor的runWorker方法。操作系統

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {...}

 

Worder一方面實現了Runnable,另外一方面又繼承了AQS。經過實現AQS,Worker具備了排它鎖的語義,每次在執行提交任務時都會先lock操做,執行完任務後再作unlock操做。正是這個加鎖與解鎖的操做,保證了同一個線程要執行完當前任務纔有機再去執行另外一個任務。.net

ThreadPoolExecutor中關閒線程池的方法shutdown與shutdownNow的區別是什麼?

shutdown方法是將線程池的狀態設置爲SHUTDOWN,此時新任務不能被提交(提交會拋出異常),workerQueue的任務會被繼續執行,同時線程池會向那些空閒的線程發出中斷信號。空閒的線程實際就不沒在執行任務的線程。如何被封裝在worker裏的線程能加鎖,這裏這個線程實現會就空閒的。下面是向空閒的線程發出中斷信號源碼。線程

 private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //w.tryLock()用於加鎖,看線程是否在執行任務
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

 

shutdownNow方法是將線程池的狀態設置爲STOP,此時新任務不能被提交(提交會拋出異常),線程池中全部線程都會收到中斷的信號。具體線程會做出什麼響應,要看狀況,若是線程由於調用了Object的wait、join方法或是自身的sleep方法而阻塞,那麼中斷狀態會被清除,同時拋出InterruptedException。其它狀況能夠參考Thread.interrupt方法的說明。shutdownNow方法向全部線程發出中斷信息源碼以下:代理

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    //加鎖操做保證中斷過程當中不會新woker被建立
    mainLock.lock();
    try {
         for (Worker w : workers)
           w.interruptIfStarted();
    } finally {
          mainLock.unlock();
    }
}

  

經過submit方法向ThreadPoolExecutor提交任務後,當全部的任務都執行完後不調用shutdown或shutdownNow方法會有問題嗎?

若是沒指核心線程容許超時將會有問題。核心線程容許超時是指在從wokerQueue中獲取任務時,採用的阻塞的獲取方式等待任務到來,仍是經過設置超時的方式從同步阻塞隊列中獲取任務。便是統統過BlockingQueue的poll方法獲取任務仍是take方法獲取任務。可參考以前的源碼分析中的getTask方法分析。若是不調用shutdown或shutdownNow方法,核心線程因爲在getTask方法調用BlockingQueue.take方法獲取任務而處於一直被阻塞掛起狀態。核心線程將永遠處於Blocking的狀態,致使內存泄漏,主線程也沒法退出,除非強制kill。試着運行以下程序會發現,程序沒法退出。

public class Test {
    public static void main(String args[]) {
        ExecutorService executorService = new ThreadPoolExecutor(3, 3,10L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10));
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread name " + Thread.currentThread().getName());
            }
        });
    }
}

 

所在使用線程池時必定要記得根本具體場景調用shutdown或shutdownNow方法關閉線程池。shutdown方法適用於提交任務都要被執行完的場景,shutdownNow方法適用於不關心提交任務是否執行完的場景。

ThreadPoolExecutor有沒有提供擴展點,方便在任務執行前或執行後作一些事情?

線程池提供了三個擴展點,分別是提交任務的run方法或是call方法被調用前與被調後,即beforeExecutor與afaterExecutor方法;另一個擴展點是線程池的狀態從TIDYING狀態流轉爲TERMINATED狀態時terminated方法會被調用。

總結

原本只是想寫一點點,寫着寫着就發現又有點長。這篇主要是介紹了ThreadPoolExecutor中我的認爲比較重要點,同時也是把ThreadPoolExecutor再梳理一下發現本身以前理解有誤差的地方。

看完三件事❤️

若是你以爲這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

  1. 點贊,轉發,有大家的 『點贊和評論』,纔是我創造的動力。

  2. 關注公衆號 『 java爛豬皮 』,不按期分享原創知識。

  3. 同時能夠期待後續文章ing🚀

 

做者:葉易
出處:club.perfma.com/article/191…

相關文章
相關標籤/搜索