前言
前文JAVA中的阻塞隊列和非阻塞隊列我們介紹了常用的幾種隊列,隊列的使用很廣泛,特別是一些需要生產消費模式的場景以及需要對全局的集合進行操作的場景。今天我們來講講其中的一種應用——線程池。
有三種常見的創建線程的方法:繼承Thread類、實現Runnable接口和實現Callable接口。這些線程在運行結束後都會被虛擬機銷毀,如果線程數量多的話,頻繁的創建和銷毀線程會大大浪費時間和效率。更重要的是浪費內存,當線程執行完畢後死亡,線程對象就變成垃圾,造成GC的頻繁收集和停頓。
我們使用線程池來解決這個問題,讓線程運行完不立即銷毀,並且重複使用,繼續執行其他的任務。使用線程池來管理線程,一方面使線程的創建更加規範,可以合理控制開闢線程的數量;另一方面線程的細節管理交給線程池處理,優化了資源的開銷。
核心類
在java.util.concurrent包中我們能找到線程池的定義,其中ThreadPoolExecutor是我們線程池的核心類,我們先看下構造函數。
構造函數的參數含義:
corePoolSize:指定了線程池中的線程數量,它的數量決定了添加的任務是開闢新的線程去執行,還是放到workQueue任務隊列中去;maximumPoolSize:指定了線程池中的最大線程數量,這個參數會根據你使用的workQueue任務隊列的類型,決定線程池會開闢的最大線程數量;keepAliveTime:當線程池中空閒線程數量超過corePoolSize時,多餘的線程會在多長時間內被銷毀;unit:keepAliveTime的單位;workQueue:存放提交的任務,實現隊列的方式有:BlockingQueue、LinkedBlockingQueue、SynchronousQueue、SynchronousQueue等,關於隊列的選擇要根據實際情況來確定;threadFactory:線程工廠,用於創建線程,一般用默認即可;handler:拒絕策略;創建線程時,為了防止資源被耗盡,任務隊列都會選擇創建有界任務隊列,但這種模式下如果出現隊列已滿且線程池創建的線程數達到最大的線程數時,就需要用拒絕策略來處理線程池「超載」的情況。jdk默認的處理方式是AbortPolicy,拋出異常阻止程序(除非是安全性要求極高,否則在大並發情況下使用這種做法不是很明智);DiscardPolicy,丟棄無法處理的任務;DiscardOledesPolicy,也是丟棄任務,只不過丟棄的是隊列最先被添加進去,馬上要執行的任務;CallerRunsPolicy,由調用者所在線程來運行任務。除了使用jdk提供的這四種策略之外,我們還可以通過實現RejectExecutionHandler來自定義拒絕策略。一般流程圖:
當線程池中線程數小於corePoolSize時,新提交的任務將創建一個新線程執行任務,即使此時線程池中存在空閒線程;當線程池中線程數達到corePoolSize時,新提交任務將被放入workQueue中,等待線程池中任務調度執行 ;當workQueue已滿,且maximumPoolSize > corePoolSize時,新提交任務會創建新線程執行任務;當workQueue已滿,且提交任務數超過maximumPoolSize,任務由RejectedExecutionHandler處理;當線程池中線程數超過corePoolSize,且超過這部分的空閒時間達到keepAliveTime時,回收這些線程;當設置allowCoreThreadTimeOut(true)時,線程池中corePoolSize範圍內的線程空閒時間達到keepAliveTime也將回收。提交線程池
兩種方式的區別:execute沒有返回值,如果不需要知道線程的結果就使用execute方法,性能比較好;submit返回一個Future對象如果想知道線程結果就使用submit提交,而且它能在主線程中通過Future的get方法捕獲線程中的異常。
關閉線程池
兩種方式的區別:shutdown不再接受新的任務,之前提交的任務等執行結束再關閉線程池;shutdownNow不再接受新的任務,試圖停止池中的任務在關閉線程池,返回所有未處理的線程List列表。
總結
線程池主要用來解決線程生命周期開銷問題和資源不足問題。通過對多個任務重複使用線程,線程創建的開銷就被分攤到多個任務上,而且由於在請求到達時線程已經存在,所以消除線程創建所帶來的延遲。這樣,就可以立即為請求服務,使應用程式響應更快。另外,通過適當的調整線程中的線程數目可以防止出現資源不足。