天天看點

Java線程池

實作Runnable接口,重寫run()方法(避免多繼承局限)

繼承Thread類,重寫run()方法(本質:Thread類也是實作Runnable接口)

實作Callable接口,重寫call()方法,有傳回值

使用線程池(使用原因:不推薦手動建立線程,不友善管理,易造成較大開銷或浪費)

在Java中,我們可以利用多線程來最大化地壓榨CPU多核計算的能力。但是,線程本身是把雙刃劍,我們需要知道它的利弊,才能在實際系統中遊刃有餘地運用。

線程池,本質上是一種對象池,用于管理線程資源。 在任務執行前,需要從線程池中拿出線程來執行。在任務執行完成之後,需要把線程放回線程池。通過線程的這種反複利用機制,可以有效地避免直接建立線程所帶來的壞處。

不使用線程池的壞處:

頻繁的線程建立和銷毀會占用更多的CPU和記憶體;

頻繁的線程建立和銷毀會對GC産生比較大的壓力;

線程太多,線程切換帶來的開銷将不可忽視;

線程太少,多核CPU得不到充分利用,是一種浪費。

線程池的好處:

降低資源的消耗。線程本身是一種資源,建立和銷毀線程會有CPU開銷;建立的線程也會占用一定的記憶體;

提高任務執行的響應速度。任務執行時,可以不必等到線程建立完之後再執行;

提高線程的可管理性。線程不能無限制地建立,需要進行統一的配置設定、調優和監控。

是以,我們有必要對線程池進行比較完整地說明,以便能對線程池進行正确地治理。

Java線程池

線程池主要處理流程

通過上圖,我們看到了線程池的主要處理流程。我們的關注點在于,任務送出之後是怎麼執行的。大緻如下:

判斷核心線程池是否已滿,如果不是,則建立線程執行任務;

如果核心線程池滿了,判斷隊列是否滿了,如果隊列沒滿,将任務放在隊列中;

如果隊列滿了,則判斷線程池是否已滿,如果沒滿,建立線程執行任務;

如果線程池也滿了,則按照拒絕政策對任務進行處理。

在jdk裡面,我們可以将處理流程描述得更清楚一點。來看看<code>ThreadPoolExecutor</code>的處理流程。

Java線程池

ThreadPoolExecutor的處理流程:

<code>corePool</code> -&gt; 核心線程池

<code>maximumPool</code> -&gt; 線程池

<code>BlockQueue</code> -&gt; 隊列

<code>RejectedExecutionHandler</code> -&gt; 拒絕政策

Executors類建立線程池的方法歸根結底都是調用ThreadPoolExecutor類,隻不過對每個方法指派不同的參數去構造ThreadPoolExecutor對象。

newCachedThreadPool:建立一個可緩存的線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則建立線程。

newFixedThreadPool:建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待

newScheduledThreadPool:建立一個定長線程池,支援定時及周期性任務執行。

newSingleThreadExecutor:建立一個單線程化的線程池,它隻會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

注意:線程池不允許使用Executors去建立,而是通過ThreadPoolExecutor的方式建立。

原因:上述四個方法的建立的隊列大小預設都是Integer.MAX_VALUE,堆積過多的任務請求會可能導緻OOM。

corePoolSize: 常駐核心線程數,如果大于0,即使本地任務執行完也不會被銷毀

maximumPoolSize: 線程池能夠容納可同時執行的最大線程數

keepAliveTime: 線程池中線程空閑的時間,當空閑時間達到該值時,線程會被銷毀, 隻剩下 corePoolSize 個線程數量。

unit: 空閑時間的機關。一般以TimeUnit類定義時分秒。

workQueue: 當請求的線程數大于 corePoolSize 時,線程進入該阻塞隊列。

LinkedBlockingQueue:無界隊列,當不指定隊列大小時,将會預設為Integer.MAX_VALUE大小的隊列,是以大量的任務将會堆積在隊列中,最終可能觸發OOM。

ArrayBlockingQueue:有界隊列,基于數組的先進先出隊列,此隊列建立時必須指定大小。

PriorityBlockingQueue:有界隊列,基于優先級任務的,它是通過Comparator決定的。

SynchronousQueue:這個隊列比較特殊,它不會儲存送出的任務,而是将直接建立一個線程來執行新來的任務

threadFactory: 線程工廠,用來生産一組相同任務的線程,同時也可以通過它增加字首名,虛拟機棧分析時更清晰

handler: 執行拒絕政策,當 workQueue 已滿,且超過maximumPoolSize 最大值,就要通過這個來處理,比如拒絕,丢棄等,這是一種限流的保護措施。

AbortPolicy:預設的拒絕政策,抛RejectedExecutionException異常

DiscardPolicy:相當大膽的政策,直接丢棄任務,沒有任何異常抛出

DiscardOldestPolicy:丢棄最老的任務,其實就是把最早進入工作隊列的任務丢棄,然後把新任務加入到工作隊列

CallerRunsPolicy:送出任務的線程自己去執行該任務

線程池關閉

shutdown() : 不會立刻終止線程,等所有緩存隊列中的任務都執行完畢後才會終止。

shutdownNow() : 立即終止線程池,并嘗試打斷正在執行的任務,并且清空任務緩存隊列,傳回尚未執行的任務

線程池監控

long getTaskCount():擷取已經執行或正在執行的任務數

long getCompletedTaskCount():擷取已經執行的任務數

int getLargestPoolSize():擷取線程池曾經建立過的最大線程數,根據這個參數,我們可以知道線程池是否滿過

int getPoolSize():擷取線程池線程數

int getActiveCount():擷取活躍線程數(正在執行任務的線程數)

注意需要引入guava包,否則ThreadFactoryBuilder會報錯

繼續閱讀