java線程池詳解


大多數學過java的童鞋都知道,創建線程有三種方式,分別是:直接繼承Thread類、實現Runnable接口、Callable接口。今天,這篇文章將幫助你學習到線程的第四種創建方式–線程池。
首先,爲什麼會提出線程池的概念呢?使用線程池有哪些優點呢?

線程池的三個優點

1.降低資源消耗:通過重複利用已創建的線程來降低線程創建和銷燬帶來的消耗。
2.提高響應速度:當任務到達時,任務可以不需要等待線程創建就能立即執行。
3.提高線程的可管理性:使用線程池可以統一進行線程分配、調度和監控。

線程池的繼承關係

在這裏插入圖片描述

倆個接口

從上圖看出,線程池有倆個重要接口。
1.ExecutorService:普通調度池
2.ScheduledExecutorService:定時調度池

倆個類

1.ThreadPoolExecutor類,線程池核心類
2框架的工具類executors

線程池的實現原理

在學習線程池實現原理之前,我們需要有一下倆個概念。
1.線程池由倆部分組成:任務隊列,工作線程。
2.線程池四大組件:核心池、阻塞隊列、最大線程池、拒絕策略。
通過這倆個概念,可以幫我們更加理解線程池的實現原理
3.線程池的實現原理:
①當任務到達,首先查看核心池是否已滿,如果沒有滿,無論此時核心池中有沒有空閒線程,都會創建新的線程來執行任務,然後將線程置入核心池;如果線程池滿了,進入②。
②查看阻塞隊列是否已滿,如果未滿,則將任務置入阻塞隊列;否則轉入③。
③判斷最大線程池是否已滿,如果未滿,無論此時核心池中是否有空閒線程,都會創建新的線程執行任務並將線程置入最大線程池;如果最大線程池滿了,轉入④。
④調用相應拒絕策略處理任務。(默認爲AbortPolicy)

線程池的創建

1.通過ThreadPoolExecutor的構造方法

public ThreadPoolExecutor(int corePoolSize,
					      int maximumPoolSize,
					      int keepAliveTime,
					      TimeUnit unit,
					      BlockingQueue<Runnable> workQueue,
					      RejectExecutionHandler handler)

1.corePoolSize:線程池的基本大小(核心池大小)–>聯繫線程池實現原理第一步。
2.maximumPoolSize:線程池最大數量(最大線程池)–>聯繫線程池原理第二步。
3.keepAliveTime:線程池的工作線程空閒後的最大存活時間。
4.unit:存活時間的單位。天:DAYS。小時HOURS。分鐘MINUTES,毫秒MILLISECONDS,微妙MICROSECONDS。
5.workQueue:工作隊列,用於保存等待執行的任務的阻塞隊列。主要有四種隊列:

a.ArrayBlockingQueue:基於數組的有界阻塞隊列,按照先進先出排序,效率較低。
b.LinkedBlockingQueue:基於鏈表的無界阻塞隊列,先進先出,吞吐量高於ArrayBlickingQueue。固定大小線程池和單線程池的工作隊列都是此隊列。
c.SynchrousQueue:不存儲元素的阻塞隊列,每個插入操作必須等待另一個線程調用移出操作,否則插入阻塞。吞吐量高於LinkedBlockingQueue。內置緩存線程池使用此隊列。
d.priorityQueue:基於小堆的優先級隊列。

6.handler最大線程池已滿,線程飽和,必須採取策略處理提交的任務。默認AbortPolicy:無法處理新任務拋異常。
JDK1.5中提供了4種策略

a.AbortPolicy:直接拋異常。
b.CallerRunsPolicy:只用調用者所在線程來執行任務。
c.DiscaredOldestPolicy:丟棄隊列中最近一個任務,執行當前任務。
d.DiscardPolicy:不處理丟棄掉。

2.創建線程池語句

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
												3,5
												2000,TimeUnit.MILLISECONDS,
												new LinkedBlockingQueue<Runnable>());
												

向線程池提交任務。

向線程池提交任務,有倆種方法,一個是execute()、一個是submit()。

1.execute()方法

1.用於提交不需要返回值的任務,所以無法判斷任務是否執行成功。
2.只可接受Runnable線程。

2.submit()方法

1.用於提交需要返回值的任務,返回的是一個future對象,可以通過future對象來判斷任務是否執行成功。同時future的get()方法可以獲取返回值。get()會阻塞當前線程直到任務完成。
2.可以接受Runnable和Callable線程。

3.倆個方法的具體實踐

//使用execute()提交任務
class RunnableThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i < 10;i++){
            System.out.println(Thread.currentThread().getName()+"、"+i);
        }
    }
}
public class ThreadPoolTest {
    public static void main(String[] args) {
        RunnableThread runnableThread = new RunnableThread();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,2000,TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        for(int i = 0;i < 5;i++){
            threadPoolExecutor.execute(runnableThread);
        }
    }
}
//使用submit()提交任務
class CallableThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        for(int i = 0;i < 10;i++){
            System.out.println(Thread.currentThread().getName()+","+i);
        }
        return Thread.currentThread().getName()+"任務執行完畢";
    }
}
public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,2000,TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        CallableThread callableThread = new CallableThread();
        for(int i = 0;i < 5;i++){
            Future<String> future = threadPoolExecutor.submit(callableThread);
            try {
                String str = future.get();
                System.out.println(str);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

關閉線程池

對於線程池的關閉,有倆個方法可以使用,分別是:shutdown()和shutdownNow()

1.原理:

遍歷線程池中所有工作線程,然後逐個調用線程的interrup()方法來中斷線程。因此無法響應中斷的線程將無法關閉。

1.shutdown()

首先將線程池狀態置爲SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程。不接受新的任務的提交,但是會繼續處理等待隊列中的任務。

2.shutdownNow()

首先將線程池的狀態置爲STOP狀態,然後停止所有正在執行的任務暫停服務的線程,並返回等待執行的任務列表。不接受新的任務的提交,不再處理等待隊列中的任務,中斷正在執行任務的線程。

3.檢查關閉–補充

調用關閉方法,isShutdown()返回true。當所有任務都關閉後,才表示線程池關閉成功,這是isTerminaed()返回true。

Executor框架

1.產生

java中線程異步執行任務,java線程的創建和銷燬需要一定的時間,如果每個任務都要創建一個線程來執行,這些線程的撤銷和創建將消耗大量的資源,也可能會使處於高負荷狀態的應用最終崩潰
java線程不僅是工作單元,還是執行機制。JDK1.5開始,將工作單元與執行機制分開。工作單元包括Runnable和Callable,而執行機制由Executor框架提供。

2.Executor的倆層調度模型

在上層,Java多線程程序會將程序分解爲若干個任務,用戶級調度器Executor會將這些任務映射爲固定數量的線程
在底層操作系統內核會將這些線程映射到硬件處理器CPU上。

3.ThreadPoolExecutor詳解

Executor框架最核心的類ThreadPoolExecutor,通過Executor框架的工具類executors,可創建三種類型的ThreadPoolExecutor。

①創建固定大小的線程池:public static ExecutorService newFixedThreadPool(int nthread);
②創建無大小限制的線程池:public static ExecutorService newCachedThreadPool();
③單線程池:public static ExecutorService newSingleThreadExecutor();
3.1JDK內置的四大線程池
3.1.1FixedThreadPool–可重用固定大小線程池
public static ExecutorService newFixedThreadPool(int nthread){
	return new ThreadPoolExecutor(nthread,nthread,0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>())}

1.使用LinkedBlockingQueue作爲線程池的工作隊列。
2.使用此隊列的影響:
①線程池的線程數不會超過corePoolSize。
②由於①,maximumSize是一個無效數。
③keepAliveTime爲0.是一個無效數。
④運行中的FixedThreadPool(未執行方法shutdown()或shutdownNow())不會拒絕任務。
3.實例

 ExecutorService executorService = Executors.newFixedThreadPool(5);
        for(int i = 0;i < 5;i++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    for(int i = 0;i < 10;i++){
                        System.out.println(Thread.currentThread().getName()+","+i);
                    }
                }
            });
        }
        executorService.shutdown();

3.應用場景
適用於爲了滿足資源管理的需求,而需要限制當前線程數量的應用場合,適用於負載較重或執行長期任務(任務執行速度慢)的服務器。

3.1.2 SingleThreadExecutor–使用單個工作線程-單線程池

1.corePoolSize被設置爲1,其他參數與FixedThreadPool相同。使用LinkedBlockingQueue無界隊列。
2.應用場景
適用於需要保證順序的執行各個任務,並且在任意時間點,不會有多個線程是活動的場景。
3.實踐

ExecutorService e = Executors.newSingleThreadExecutor();
3.1.3 CacheThreadPool–根據需要創建新的線程的線程池-緩存線程池

1.corePoolSize爲0,即爲空。
2.maximumPoolSize爲Inteder_max(最大)。如果線程提交任務的速度遠遠大於最大線程池中線程處理任務的速度時,CacheThreadPool會不斷創建新的線程。極端情況下,緩存池會因創建過多線程而耗盡CPU和內存資源。
3.使用synchrousQueue
4.keepAliveTime被設置爲60s。
3.應用場景
適用於負載較輕的服務器或短期的異步小任務。
4.實踐

ExecutorService e = Executors.newCacheThreadPool();
3.1.4 ScheduledThreadPoolExecutor–定時調度池

使用DelayQueue無界隊列,執行定時任務。

ScheduledExecutorService e = Executors.scheduledAtFixedRate(thread,2,3,TimeUnit.Seconds)

無提交無關閉。

線程池常見面試題

1.創建線程池有哪幾種方式

有7中
ThreadPoolExecutor()
最原始的線程池創建,以下2,3,4,是對它的封裝。
newFixedThreadPool(int nthreads)
可重用的指定線程數目的線程池,其背後使用的是無界隊列LinkedBlockingQueue,任意時刻最多有nthreads個工作線程是活動的。這意味着,如果任務數量超過活動隊列數目,將在工作隊列中等待空閒線程出現,如果工作線程退出,將會有新的工作隊列被創建,以補足指定數目的nthreads。
newCachThreadPool()
它的corePoolSize爲0,maximumPoolSize爲最大值。如果主線程提交任務的速度遠遠大於最大池中線程處理任務的速度時,會不斷創建新的線程。極端情況下會耗盡CPU資源。如果線程閒置時間超過60s,則會被終止移出緩存,長時間閒置時,這種線程池不會消耗什麼資源。synchrousQueue作爲工作隊列。
newSingleThreadExecutor()
他的特點是核心池和最大線程池大小都爲1,操作無界隊列LinkedBlockingQueue,所以他保證多有任務都會被順序執行。某一時刻,最多有一個線程處於活動狀態,並且不允許使用者改動線程池實例,因此可以避免其改變線程數目。
newScheduledThreadPool()
創建的是一個ScheduledExecutorService,可以進行定時或週期性的工作調度。
newSingleThreadScheduledExecutor()
創建單線程池,返回ScheduledExecutorService,可以進行定時或週期性的工作調度。
newWorkStealingPool(int paralleism)
JDK8加入,內部創建ForkJoinPool,利用work-stealing算法,並行的處理任務,不保證處理順序。

2.線程池有哪些狀態

1.RUNNING:最正常的狀態,接受新的任務,處理等待隊列中的任務
2.SHUTDOWN:不接受新的任務提交,但是會繼續處理等待隊列中的任務。
3.STOP:不接受新的任務提交,不再處理等待隊列中的任務,中斷正在執行任務的線程。
4.TIDYING:所有線程都銷燬了,workcount爲0,線程池狀態爲TIDYING時,會執行鉤子方法terminated()。
5.TERMINATED:terminated()方法結束後,線程的狀態。

3.execute()和submit()的區別

1.execute():用來提交不需要返回值的任務。可執行Runnable類型的任務。
2.submit():用來提交需要返回值的任務,返回一個future對象,通過future的get(0來獲取返回值。可執行Runnable和Callable任務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章