摘要:本文主要內容為簡單總結中線程池的相關信息。方法簇方法簇用于創(chuàng)建固定線程數的線程池。三種常見線程池的對比上文總結了工具類創(chuàng)建常見線程池的方法,現對三種線程池區(qū)別進行比較。
概述
線程可認為是操作系統(tǒng)可調度的最小的程序執(zhí)行序列,一般作為進程的組成部分,同一進程中多個線程可共享該進程的資源(如內存等)。在單核處理器架構下,操作系統(tǒng)一般使用分時的方式實現多線程;在多核處理器架構下,多個線程能夠做到真正的在不同處理核心并行處理。
無論使用何種方式實現多線程,正確使用多線程都可以提高程序性能,或是吞吐量,或是響應時間,甚至兩者兼具。如何正確使用多線程涉及較多的理論及最佳實踐,本文無法詳細展開,可參考如《Programming Concurrency on the JVM》等書籍。
本文主要內容為簡單總結Java中線程池的相關信息。
Java中提供Thread作為線程實現,一般有兩種方式:
直接集成Thread類:
class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } class Starter{ public static void main(){ PrimeThread p = new PrimeThread(143); p.start(); } }
實現Runnable 接口:
class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } class Starter{ public static void main(){ PrimeRun p = new PrimeRun(143); new Thread(p).start(); } }
線程是屬于操作系統(tǒng)的概念,Java中的多線線程實現一定會依托于操作系統(tǒng)支持。HotSpot虛擬機中對多線程的實現實際上是使用了一對一的映射模型,即一個Java進程映射到一個輕量級進程(LWP)之中。在使用Thread的start方法后,HotSpot創(chuàng)建本地線程并與Java線程關聯。在此過程之中虛擬機需要創(chuàng)建多個對象(如OSThread等)用于跟蹤線程狀態(tài),后續(xù)需要進行線程初始化工作(如初始換ThreadLocalAllocBuffer對象等),最后啟動線程調用上文實現的run方法。
由此可見創(chuàng)建線程的成本較高,如果線程中run函數中業(yè)務代碼執(zhí)行時間非常短且消耗資源較少的情況下,可能出現創(chuàng)建線程成本大于執(zhí)行真正業(yè)務代碼的成本,這樣難以達到提升程序性能的目的。
由于創(chuàng)建線程成本較大,很容易想到通過復用已創(chuàng)建的線程已達到減少線程創(chuàng)建成本的方法,此時線程池就可以發(fā)揮作用。
Java線程池主要核心類(接口)為Executor,ExecutorService,Executors等,具體關系如下圖所示:
由以上類圖可見在線程池類結構體系中Executor作為最初始的接口,該接口僅僅規(guī)定了一個方法void execute(Runnable command),此接口作用為規(guī)定線程池需要實現的最基本方法為可運行實現了Runnable接口的任務,并且開發(fā)人員不需要關心具體的線程池實現(在實際使用過程中,仍需要根據不同任務特點選擇不同的線程池實現),將客戶端代碼與運行客戶端代碼的線程池解耦。
ExecutorService接口Executor接口雖然完成了業(yè)務代碼與線程池的解耦,但沒有提供任何與線程池交互的方法,并且僅僅支持沒有任何返回值的Runnable任務的提交,在實際業(yè)務實現中功能略顯不足。為了解決以上問題,JDK中增加了擴展Executor接口的子接口ExecutorService。
ExecutorService接口主要在兩方面擴展了Executor接口:
提供針對線程池的多個管理方法,主要包括停止任務提交、停止線程池運行、判斷線程池是否停止運行及線程池中任務是否運行完成;
增加submit的多個重載方法,該方法可在提交運行任務時,返回給提交任務的線程一個Future對象,可通過該對象對提交的任務進行控制,如取消任務或獲取任務結果等(Future對象如何實現此功能另行討論)。
Executors工具類Executors是主要為了簡化線程池的創(chuàng)建而提供的工具類,通過調用各靜態(tài)工具方法返回響應的線程池實現。通過對其方法的觀察可將其提供的工具方法歸為如下幾類:
創(chuàng)建ExecutorService對象的工具:又可細分為創(chuàng)建FixedThreadPool、SingleThreadPool、CachedThreadPool、WorkStealingPool、UnconfigurableExecutorService、SingleThreadScheduledExecutor及ThreadScheduledExecutor;
創(chuàng)建ThreadFactory對象;
將Runnable等對象封裝為Callable對象。
以上各工具方法中使用最廣泛的為newCachedThreadPool、newFixedThreadPool及newSingleThreadExecutor,這三個方法創(chuàng)建的ExecutorService對象均是其子類ThreadPoolExecutor(嚴格來說newSingleThreadExecutor方法返回的是FinalizableDelegatedExecutorService對象,其封裝了ThreadPoolExecutor,為何如此實現后文在做分析),下文著重分析ThreadPoolExecutor類。至于其他ExecutorService實現類,如ThreadScheduledExecutor本文不做詳細分析。
ThreadPoolExecutor類ThreadPoolExecutor類是線程池ExecutorService的重要實現類,在工具類Executors中構建的線程池對象,有大部分均是ThreadPoolExecutor實現。
ThreadPoolExecutor類提供多個構造參數對線程池進行配置,代碼如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
現在對各個參數作用進行總結:
參數名稱 | 參數類型 | 參數用途 |
---|---|---|
corePoolSize | int | 核心線程數,線程池中會一直保持該數量的線程,即使這些線程是空閑的狀態(tài),如果設置allowCoreThreadTimeOut屬性(默認為false)為true,則空閑超過超時時間的核心線程可以被回收 |
maximumPoolSize | int | 最大線程數,當前線程池中可存在的最大線程數 |
keepAliveTime | long | 線程存活時間,當當前線程池中線程數大于核心線程數時,空閑線程等待新任務的時間,超過該時間則停止空閑線程 |
unit | TimeUnit | 時間單位,keepAliveTime屬性的時間單位 |
workQueue | BlockingQueue |
等待隊列,存儲待執(zhí)行的任務 |
threadFactory | ThreadFactory | 線程工廠,線程池創(chuàng)建線程時s使用 |
handler | RejectedExecutionHandler | 拒絕執(zhí)行處理器,當提交任務被拒絕(當等待隊列滿,且線程達到最大限制后)時調用 |
在使用該線程池時有一個重要的參數起效順序:
提交任務時,當當前運行的線程數小于核心線程時,則啟動新的線程執(zhí)行任務;
提交任務時,當前運行線程數大于等于核心線程數,將當前任務加入等待隊列中;
將任務添加到等待隊列失敗時(如隊列滿),嘗試新建線程運行任務;
新建線程時,線程池關閉或達到最大線程數,則拒絕任務,調用handler進行處理。
ThreadFactory有默認的實現為Executors.DefaultThreadFactory,其創(chuàng)建線程主要額外工作為將新建的線程加入當前線程組,并且將線程的名稱置為pool-x-thread-y的形式。
ThreadPoolExecutor類通過內部類的形式提供了四種任務被拒絕時的處理器:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy及DiscardPolicy。
拒絕策略類 | 具體操作 |
---|---|
AbortPolicy | 拋出RejectedExecutionException異常,拒絕執(zhí)行任務 |
CallerRunsPolicy | 在提交任務的線程執(zhí)行當前任務,即在調用函數execute或submit的線程直接運行任務 |
DiscardOldestPolicy | 直接取消當前等待隊列中最早的任務 |
DiscardPolicy | 以靜默方式丟棄任務 |
ThreadPoolExecutor默認使用的是AbortPolicy處理策略,用戶可自行實現RejectedExecutionHandler接口自定義處理策略,本處不在贅述。
Executors對于ThreadPoolExecutor的創(chuàng)建根據上文描述,Executors類提供了較多的關于創(chuàng)建或使用線程池的工具方法,此節(jié)重點總結其在創(chuàng)建ThreadPoolExecutor線程池的各方法。
newCachedThreadPool方法簇newCachedThreadPool方法簇用于創(chuàng)建可緩存任務的ThreadPoolExecutor線程池。包括兩個重構方法:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); } public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue (), threadFactory); }
結合上文分析的ThreadPoolExecutor各構造參數,可總結如下:
核心線程數為0:沒有核心線程,即在沒有任務運行時所有線程均會被回收;
最大線程數為Integer.MAX_VALUE,即線程池中最大可存在的線程為Integer.MAX_VALUE,由于此值在通常情況下遠遠大于系統(tǒng)可新建的線程數,可簡單理解為此線程池不限制最大可建的線程數,此處可出現邏輯風險,在提交任務時可能由于超過系統(tǒng)處理能力造成無法再新建線程時會出現OOM異常,提示無法創(chuàng)建新的線程;
存活時間60秒:線程數量超過核心線程后,空閑60秒的線程將會被回收,根據第一條可知核心線程數為0,則本條表示所有線程空閑超過60秒均會被回收;
等待隊列SynchronousQueue:構建CachedThreadPool時,使用的等待隊列為SynchronousQueue類型,此類型的等待隊列較為特殊,可認為這是一個容量為0的阻塞隊列,在調用其offer方法時,如當前有消費者正在等待獲取元素,則返回true,否則返回false。使用此等待隊列可做到快速提交任務到空閑線程,沒有空閑線程時觸發(fā)新建線程;
ThreadFactory參數:默認為DefaultThreadFactory,也可通過構造函數設置。
newFixedThreadPool方法簇newFixedThreadPool方法簇用于創(chuàng)建固定線程數的ThreadPoolExecutor線程池。包括兩個構造方法:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue (), threadFactory); }
各構造參數總結:
核心線程數與最大線程數nThreads:構建的ThreadPoolExecutor核心線程數與最大線程數相等且均為nThreads,這說明當前線程池不會存在非核心線程,即不會存在線程的回收(allowCoreThreadTimeOut默認為false),隨著任務的提交,線程數增加到nThreads個后就不會變化;
存活時間為0:線程存在非核心線程,該時間沒有特殊效果;
等待隊列LinkedBlockingQueue:該等待隊列為LinkedBlockingQueue類型,沒有長度限制;
ThreadFactory參數:默認為DefaultThreadFactory,也可通過構造函數設置。
newSingleThreadExecutor方法簇newSingleThreadExecutor方法簇用于創(chuàng)建只包含一個線程的線程池。包括兩個構造方法:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue (), threadFactory)); }
結合上文分析的ThreadPoolExecutor各構造參數,可總結如下:
核心線程數與最大線程數1:當前線程池中有且僅有一個核心線程;
存活時間為0:當前線程池不存在非核心線程,不會存在線程的超時回收;
等待隊列LinkedBlockingQueue:該等待隊列為LinkedBlockingQueue類型,沒有長度限制;
ThreadFactory參數:默認為DefaultThreadFactory,也可通過構造函數設置。
特殊說明,函數實際返回的對象類型并不是ThreadPoolExecutor而是FinalizableDelegatedExecutorService類型,為何如此設計在后文統(tǒng)一討論。
三種常見線程池的對比上文總結了Executors工具類創(chuàng)建常見線程池的方法,現對三種線程池區(qū)別進行比較。
線程池類型 | CachedThreadPool | FixedThreadPool | SingleThreadExecutor |
---|---|---|---|
核心線程數 | 0 | nThreads(用戶設定) | 1 |
最大線程數 | Integer.MAX_VALUE | nThreads(用戶設定) | 1 |
非核心線程存活時間 | 60s | 無非核心線程 | 無非核心線程 |
等待隊列最大長度 | 1 | 無限制 | 無限制 |
特點 | 提交任務優(yōu)先復用空閑線程,沒有空閑線程則創(chuàng)建新線程 | 固定線程數,等待運行的任務均放入等待隊列 | 有且僅有一個線程在運行,等待運行任務放入等待隊列,可保證任務運行順序與提交順序一直 |
內存溢出 | 大量提交任務后,可能出現無法創(chuàng)建線程的OOM | 大量提交任務后,可能出現內存不足的OOM | 大量提交任務后,可能出現內存不足的OOM |
一般情況下JVM中的GC根據可達性分析確認一個對象是否可被回收(eligible for GC),而在運行的線程被視為‘GCRoot’。因此被在運行的線程引用的對象是不會被GC回收的。在ThreadPoolExecutor類中具有f非靜態(tài)內部類Worker,用于表示x當前線程池中的線程,并且根據Java語言規(guī)范An instance i of a direct inner class C of a class or interface O is associated with an instance of O, known as the immediately enclosing instance of i. The immediately enclosing instance of an object, if any, is determined when the object is created (§15.9.2).可知非靜態(tài)內部類對象具有外部包裝類對象的引用(此處也可通過查看字節(jié)碼來驗證),因此Worker類的對象即作為線程對象(‘GCRoot’)有持有外部類ThreadPoolExecutor對象的引用,則在其運行結束之前,外部內不會被Gc回收。
根據以上分析,再次觀察以上三個線程池:
CachedThreadPool:沒有核心線程,且線程具有超時時間,可見在其引用消失后,等待任務運行結束且所有線程空閑回收后,GC開始回收此線程池對象;
FixedThreadPool:核心線程數及最大線程數均為nThreads,并且在默認allowCoreThreadTimeOut為false的情況下,其引用消失后,核心線程即使空閑也不會被回收,故GC不會回收該線程池;
SingleThreadExecutor:默認與FixedThreadPool情況一致,但由于其語義為單線程線程池,JDK開發(fā)人員為其提供了FinalizableDelegatedExecutorService包裝類,在創(chuàng)建FixedThreadPool對象時實際返回的是FinalizableDelegatedExecutorService對象,該對象持有FixedThreadPool對象的引用,但FixedThreadPool對象并不引用FinalizableDelegatedExecutorService對象,這使得在FinalizableDelegatedExecutorService對象的外部引用消失后,GC將會對其進行回收,觸發(fā)finalize函數,而該函數僅僅簡單的調用shutdown函數關閉線程,是的所有當前的任務執(zhí)行完成后,回收線程池中線程,則GC可回收線程池對象。
因此可得出結論,CachedThreadPool及SingleThreadExecutor的對象在不顯式調用shutdown函數(或shutdownNow函數),且其對象引用消失的情況下,可以被GC回收;FixedThreadPool對象在不顯式調用shutdown函數(或shutdownNow函數),且其對象引用消失的情況下不會被GC回收,會出現內存泄露。
實驗驗證以上結論可使用實驗驗證:
public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); //ExecutorService executorService = Executors.newFixedThreadPool(1); //ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(() -> System.out.println(Thread.currentThread().getName())); //線程引用置空 executorService = null; Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Shutdown."))); //等待線程超時,主要對CachedThreadPool有效 Thread.sleep(100000); //手動觸發(fā)GC System.gc(); }
使用以上代碼,分別創(chuàng)建三種不同的線程池,可發(fā)現最終FixedThreadPool不會打印出‘Shutdown.’,JVM沒有退出。另外兩種線程池均能退出JVM。
因此無論使用什么線程池線程池使用完畢后均調用shutdown以保證其最終會被GC回收是一個較為安全的編程習慣。
根據以上的原理及代碼分析,很容易提出如下問題:既然SingleThreadExecutor的實現方式可以自動完成線程池的關閉,為何不使用同樣的方式實現FixedThreadPool呢?
目前作者沒有找到確切的原因,此處引用兩個對此有所討論的兩個網址:王智超-理解SingleThreadExecutor及[Why doesn"t all Executors factory methods wrap in a FinalizableDelegatedExecutorService?
](https://stackoverflow.com/que...。
作者當前提出一種不保證正確的可能性:JDK開發(fā)人員可能重語義方面考慮將FixedThreadPool定義為可重新配置的線程池,SingleThreadExecutor定義為不可重新配置的線程池。因此沒有使用FinalizableDelegatedExecutorService對象包裝FixedThreadPool對象,將其控制權放到了程序員手中。
最后再分享一個關于SingleThreadExecutor的踩坑代碼,改代碼在編程過程中一般不會出現,但其中涉及較多知識點,不失為一個好的學習示例:
import java.util.concurrent.Callable; import java.util.concurrent.Executors; class Prog { public static void main(String[] args) { Callablecallable = new Callable () { public Long call() throws Exception { // Allocate, to create some memory pressure. byte[][] bytes = new byte[1024][]; for (int i = 0; i < 1024; i++) { bytes[i] = new byte[1024]; } return 42L; } }; for (;;) { Executors.newSingleThreadExecutor().submit(callable); } } }
以上代碼在設置-Xmx128m的虛擬機進行運行,大概率會拋出RejectedExecutionException異常,其原理與上文分析的GC回收有關,詳細分析可參考[Learning from bad code
](https://www.farside.org.uk/20...。
以上總結了使用Executors創(chuàng)建常見線程池的方法,在簡單的使用中的確方便使用且減少的手動創(chuàng)建線程池的代碼量,但在真正開發(fā)高并發(fā)程序時,其默認創(chuàng)建的線程由于屏蔽了底層參數,程序員難以真正理解其中可能出現的細節(jié)問題,包括內存溢出及拒絕策略等,故在使用中t推薦使用ThreadPoolExecutor等方式直接創(chuàng)建。此處可以參考《阿里巴巴Java開發(fā)手冊終極版v1.3.0》(六)并發(fā)處理的第4點。
總結本文簡單總結了Java線程及常用線程池的使用,對比常見線程池的特點。由于本文側重于分析使用層面,并沒有深入探究各線程池具體的代碼實現,此項可留后續(xù)繼續(xù)補充。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77197.html
摘要:并發(fā)編程實戰(zhàn)水平很高,然而并不是本好書。一是多線程的控制,二是并發(fā)同步的管理。最后,使用和來關閉線程池,停止其中的線程。當線程調用或等阻塞時,對這個線程調用會使線程醒來,并受到,且線程的中斷標記被設置。 《Java并發(fā)編程實戰(zhàn)》水平很高,然而并不是本好書。組織混亂、長篇大論、難以消化,中文翻譯也較死板。這里是一篇批評此書的帖子,很是貼切。俗話說:看到有這么多人罵你,我就放心了。 然而知...
摘要:去美團面試,問到了什么是線程池,如何使用,為什么要用以下做個總結。二線程池線程池的作用線程池作用就是限制系統(tǒng)中執(zhí)行線程的數量。真正的線程池接口是。創(chuàng)建固定大小的線程池。此線程池支持定時以及周期性執(zhí)行任務的需求。 去美團面試,問到了什么是線程池,如何使用,為什么要用,以下做個總結。關于線程之前也寫過一篇文章《高級面試題總結—線程池還能這么玩?》 1、什么是線程池:? java.util...
摘要:去美團面試,問到了什么是線程池,如何使用,為什么要用以下做個總結。二線程池線程池的作用線程池作用就是限制系統(tǒng)中執(zhí)行線程的數量。真正的線程池接口是。創(chuàng)建固定大小的線程池。此線程池支持定時以及周期性執(zhí)行任務的需求。 去美團面試,問到了什么是線程池,如何使用,為什么要用,以下做個總結。關于線程之前也寫過一篇文章《高級面試題總結—線程池還能這么玩?》 1、什么是線程池:? java.util...
摘要:一和并發(fā)包中的和主要解決的是線程的互斥和同步問題,這兩者的配合使用,相當于的使用。寫鎖與讀鎖之間互斥,一個線程在寫時,不允許讀操作。的注意事項不支持重入,即不可反復獲取同一把鎖。沒有返回值,也就是說無法獲取執(zhí)行結果。 一、Lock 和 Condition Java 并發(fā)包中的 Lock 和 Condition 主要解決的是線程的互斥和同步問題,這兩者的配合使用,相當于 synchron...
閱讀 3192·2021-11-23 10:09
閱讀 2057·2021-10-26 09:51
閱讀 974·2021-10-09 09:44
閱讀 3897·2021-10-08 10:04
閱讀 2742·2021-09-22 15:14
閱讀 3619·2021-09-22 15:02
閱讀 1035·2021-08-24 10:03
閱讀 1719·2019-12-27 12:14