虎門做網(wǎng)站公司2020最成功的網(wǎng)絡(luò)營銷
一、基本概念與關(guān)系
程序
程序是含有指令和數(shù)據(jù)的文件,靜態(tài)地存儲在磁盤等存儲設(shè)備上。它是軟件的實體,但未被激活。
進程
進程是程序的一次執(zhí)行過程,是系統(tǒng)運行程序的基本單位。當程序被操作系統(tǒng)加載并執(zhí)行時,就成為一個進程,具有動態(tài)性。進程擁有獨立的內(nèi)存空間和系統(tǒng)資源(如CPU時間、內(nèi)存、I/O設(shè)備等),是一個正在執(zhí)行中的程序?qū)嵗_M程之間相互獨立,不共享內(nèi)存空間。
線程
線程是進程內(nèi)部的一個執(zhí)行單元,是進程中實際運作的小單位,也是CPU調(diào)度的基本單位。相比進程,線程更輕量,同類線程共享同一塊內(nèi)存空間和系統(tǒng)資源。線程之間可以共享數(shù)據(jù),但也可能相互影響,特別是在同一個進程中。線程的引入提高了程序的并發(fā)執(zhí)行能力。
三者之間的關(guān)系
- 程序到進程:程序通過加載執(zhí)行轉(zhuǎn)變?yōu)檫M程,實現(xiàn)了從靜態(tài)代碼到動態(tài)執(zhí)行實體的轉(zhuǎn)變。
- 進程到線程:一個進程可以包含多個線程,這些線程共享進程的資源,在同一進程環(huán)境下并發(fā)執(zhí)行不同的任務(wù),提高了效率和響應(yīng)速度。
- 線程相比進程,降低了資源開銷,提高了通信效率,但同時也可能因為資源共享引發(fā)同步問題。
二、線程的基本狀態(tài)
Java 線程在其生命周期中會經(jīng)歷以下6種基本狀態(tài)之一,并且隨著代碼執(zhí)行和系統(tǒng)調(diào)度在這些狀態(tài)之間轉(zhuǎn)換:
- 新建(New):線程剛被創(chuàng)建,尚未啟動。
- 可運行(Runnable):線程可以被調(diào)度執(zhí)行,包括已經(jīng)獲得CPU時間片正在運行的線程和等待CPU分配時間片的線程。
- 阻塞(Blocked):線程等待某個監(jiān)視器鎖(例如進入synchronized代碼塊時),或者等待I/O操作完成等。
- 等待(Waiting):線程等待其他線程執(zhí)行特定操作,如調(diào)用
Object.wait()
方法,不可被中斷直到等待條件滿足。 - 超時等待(Timed Waiting):與等待狀態(tài)相似,但線程在指定時間后會自動醒來,如調(diào)用
Thread.sleep(long millis)
方法。 - 終止(Terminated):線程已結(jié)束執(zhí)行,無論是正常結(jié)束還是異常結(jié)束。
這些狀態(tài)描述了線程從創(chuàng)建到執(zhí)行完畢的完整生命周期,理解這些狀態(tài)對于調(diào)試多線程程序和避免死鎖等問題至關(guān)重要。
Java 線程狀態(tài)如下圖所示:
三、 線程池的原理與創(chuàng)建原因及方式
原理
線程池是一種基于池化思想管理線程的機制,其核心在于重用線程資源,減少創(chuàng)建和銷毀線程的開銷。線程池的工作流程主要包括以下幾個步驟:
- 創(chuàng)建線程池:初始化時,預(yù)先創(chuàng)建一定數(shù)量的線程并將其置于待命狀態(tài),等待任務(wù)分配。
- 任務(wù)提交:當有新任務(wù)到達時,將其加入到任務(wù)隊列中。
- 任務(wù)調(diào)度:線程池中的空閑線程會從任務(wù)隊列中取出任務(wù)并執(zhí)行。若所有線程均在忙狀態(tài),且任務(wù)隊列已滿,則根據(jù)策略決定是否創(chuàng)建新線程或拒絕任務(wù)。
- 任務(wù)執(zhí)行完畢:線程不會立即銷毀,而是返回線程池等待下一個任務(wù)。
- 線程管理:根據(jù)系統(tǒng)負載動態(tài)調(diào)整線程數(shù)量,避免過多線程導(dǎo)致資源耗盡或過少線程影響任務(wù)處理速度。
優(yōu)點
- 資源重用:通過重復(fù)利用已創(chuàng)建的線程,減少了線程創(chuàng)建和銷毀的開銷。
- 提高響應(yīng)速度:任務(wù)到達時無需等待新線程創(chuàng)建即可立即執(zhí)行。
- 管理靈活性:可根據(jù)系統(tǒng)狀況動態(tài)調(diào)整線程數(shù)量,有效控制資源使用,防止資源耗盡。
- 簡化編程模型:提供統(tǒng)一的接口給開發(fā)者提交任務(wù),隱藏了線程管理的復(fù)雜細節(jié)。
- 提高系統(tǒng)穩(wěn)定性:通過控制最大線程數(shù),防止了過多線程導(dǎo)致的資源競爭和調(diào)度開銷,增強了系統(tǒng)的穩(wěn)定性和可預(yù)測性。
創(chuàng)建線程池的方式
-
手動創(chuàng)建:基礎(chǔ)方式,使用語言提供的線程創(chuàng)建API(如Java中的
Thread
類)配合同步機制(如隊列、鎖等)自行實現(xiàn)線程池邏輯。 -
使用標準庫:
- Java: 通過
Executors
類提供的工廠方法創(chuàng)建不同類型線程池,如newFixedThreadPool
創(chuàng)建固定大小線程池,newCachedThreadPool
創(chuàng)建可緩存線程池等。 - C++/POSIX: 利用
std::thread
結(jié)合std::queue
等容器自建線程池,或使用第三方庫如boost::thread_pool
。 - 其他語言:如Python的
concurrent.futures.ThreadPoolExecutor
,Node.js的worker_threads
模塊等,都提供了線程池或類似機制的封裝。
- Java: 通過
-
第三方庫:使用成熟的第三方線程池庫,如Java的
Apache Commons ThreadPoolExecutor
,C#的Microsoft TPL(Task Parallel Library)
等,這些庫通常提供了更高級的特性,如線程池監(jiān)控、任務(wù)調(diào)度策略等。
選擇合適的創(chuàng)建方式需根據(jù)項目需求、性能要求以及目標平臺的支持程度綜合考慮。
四、線程池的創(chuàng)建與參數(shù)解析
在Java中,通過ThreadPoolExecutor
類來創(chuàng)建自定義線程池,其構(gòu)造函數(shù)提供了高度靈活的配置選項,以便根據(jù)具體需求調(diào)整線程池的行為。下面是構(gòu)造函數(shù)及其參數(shù)的詳細介紹:
public ThreadPoolExecutor(int corePoolSize, // 核心線程數(shù)int maximumPoolSize, // 最大線程數(shù)long keepAliveTime, // 空閑線程存活時間TimeUnit unit, // 存活時間的單位BlockingQueue<Runnable> workQueue, // 任務(wù)隊列RejectedExecutionHandler handler // 拒絕策略
)
參數(shù)解釋
-
corePoolSize:核心線程數(shù)
線程池中常駐的核心線程數(shù)量。即使線程空閑,這部分線程也會保留在線程池中,不會被回收。只有在工作隊列滿并且當前線程數(shù)小于最大線程數(shù)時才會創(chuàng)建新的線程。 -
maximumPoolSize:最大線程數(shù)
線程池能容納的最大線程數(shù)量。當活動線程達到這個數(shù)值后,新來的任務(wù)如果不能放入任務(wù)隊列將被拒絕處理。 -
keepAliveTime:空閑線程存活時間
當線程池中的線程數(shù)量超過核心線程數(shù)時,多余的空閑線程在等待新任務(wù)最長時間達到keepAliveTime后,如果還沒有新任務(wù)到來,那么這些線程將被終止以節(jié)省資源。 -
unit:存活時間的單位
keepAliveTime參數(shù)的時間單位,如TimeUnit.SECONDS
秒、TimeUnit.MILLISECONDS
毫秒等。 -
workQueue:任務(wù)隊列
用于保存等待執(zhí)行的任務(wù)的阻塞隊列。不同的隊列實現(xiàn)會影響線程池的行為,常見的有ArrayBlockingQueue
(固定大小)、LinkedBlockingQueue
(無界或有限界限)、SynchronousQueue
(直接傳遞,沒有容量)等。 -
handler:拒絕策略
當線程池和任務(wù)隊列都達到飽和狀態(tài)時(即無法再接受新任務(wù)),如何處理新提交的任務(wù)。JDK內(nèi)置了幾種拒絕策略:AbortPolicy
:默認策略,丟棄任務(wù)并拋出RejectedExecutionException
異常。CallerRunsPolicy
:調(diào)用者運行策略,直接在調(diào)用者線程中執(zhí)行被拒絕的任務(wù)。DiscardPolicy
:靜默丟棄任務(wù),不拋出任何異常。DiscardOldestPolicy
:丟棄隊列中最舊的任務(wù),并嘗試重新提交當前任務(wù)。
通過調(diào)整這些參數(shù),可以創(chuàng)建符合特定應(yīng)用場景需求的線程池,以達到資源最優(yōu)利用和任務(wù)高效執(zhí)行的目的。
五、 線程池原理
1. 固定大小線程池(FixedThreadPool)
特點:
固定大小線程池維護一個固定數(shù)量的線程,確保所有任務(wù)都會在一個控制好的線程集合中執(zhí)行,適合于負載較為穩(wěn)定、任務(wù)執(zhí)行時間相對均衡的場景。由于使用了無界隊列LinkedBlockingQueue,如果任務(wù)提交速率超過處理能力,隊列可能會無限增長,導(dǎo)致內(nèi)存溢出風(fēng)險。
參數(shù)分析:
-
corePoolSize:等于最大線程數(shù),一旦創(chuàng)建就不會改變,保證線程數(shù)量恒定。
-
workQueue:默認為LinkedBlockingQueue,隊列容量無上限。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class FixedThreadPoolDemo {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(3);for (int i = 0; i < 10; i++) {final int taskId = i;executor.execute(() -> {System.out.println("Task ID : " + taskId + ", Thread Name : " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}executor.shutdown();}
}
2. 可緩存線程池(CachedThreadPool)
特點:
可緩存線程池會根據(jù)任務(wù)的需求動態(tài)創(chuàng)建線程,空閑線程超過一定時間(默認60秒)則會被回收。這種線程池適合執(zhí)行大量短期異步任務(wù),能夠迅速響應(yīng)突發(fā)請求,但不適合長時間運行的任務(wù),因為線程數(shù)量可能無限增長,導(dǎo)致資源耗盡。
參數(shù)分析:
- corePoolSize=0,初始時無核心線程。
- maximumPoolSize=Integer.MAX_VALUE,理論上允許無限增長。
- keepAliveTime=1分鐘,空閑線程等待新任務(wù)的最長時間。
- workQueue=SynchronousQueue,直接傳遞,不保留任務(wù)。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CachedThreadPoolDemo {public static void main(String[] args) {ExecutorService executor = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int taskId = i;executor.execute(() -> {System.out.println("Task ID : " + taskId + ", Thread Name : " + Thread.currentThread().getName());});}executor.shutdown();}
}
3. 定時任務(wù)線程池(ScheduledThreadPool)
特點:
定時任務(wù)線程池用于執(zhí)行定時或周期性的任務(wù),如定時檢查、定時清理等。它維護了一個固定大小的核心線程池,并使用DelayedWorkQueue作為任務(wù)隊列來存放將要執(zhí)行的任務(wù)。
參數(shù)分析:
- corePoolSize:線程池的基本大小,即使沒有任務(wù)執(zhí)行,線程也會保持存活。
- 支持schedule系列方法設(shè)定任務(wù)的執(zhí)行時間或周期。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolDemo {public static void main(String[] args) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);for (int i = 0; i < 3; i++) {final int taskId = i;executor.scheduleAtFixedRate(() -> {System.out.println("Task ID : " + taskId + ", Thread Name : " + Thread.currentThread().getName());}, 0, 1, TimeUnit.SECONDS);}}
}
以上介紹了Java中三種常用的線程池類型,通過實例展示了它們的使用方法和特性。在實際應(yīng)用中,應(yīng)根據(jù)具體需求選擇合適的線程池類型,并考慮是否需要進一步自定義線程池參數(shù),以達到最佳的性能與資源利用率。盡管Executors類提供了簡便的工廠方法,但在生產(chǎn)環(huán)境中推薦直接使用ThreadPoolExecutor構(gòu)造函數(shù)來實現(xiàn)更細致的控制,以避免潛在的資源耗盡問題
六、常用的線程池
提交一個任務(wù)到線程池中,線程池的處理流程如下:
- 1、判斷線程池里的核心線程是否都在執(zhí)行任務(wù),如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)
建)則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)。如果核心線程都在執(zhí)行任務(wù),則進入下個流程。 - 2、線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務(wù)存儲在這個工作隊列里。如
果工作隊列滿了,則進入下個流程。 - 3、判斷線程池里的線程是否都處于工作狀態(tài),如果沒有,則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)。如果已經(jīng)滿了,則交給飽和策略來處理這個任務(wù)。
1、ThreadPoolExecutor的execute()方法
public void execute(Runnable command) {if (command == null) {throw new NullPointerException();}// 如果線程數(shù)大于等于核心線程數(shù)或者線程創(chuàng)建失敗,將任務(wù)加入隊列if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {// 線程池處于運行狀態(tài)并且加入隊列成功if (runState == RUNNING && workQueue.offer(command)) {if (runState != RUNNING || poolSize == 0) {ensureQueuedTaskHandled(command);}} // 線程池不處于運行狀態(tài)或者加入隊列失敗,則創(chuàng)建線程(創(chuàng)建的是非核心線程)else if (!addIfUnderMaximumPoolSize(command)) {// 創(chuàng)建線程失敗,則采取阻塞處理的方式reject(command); // is shutdown or saturated}}
}
2、創(chuàng)建線程的方法:addIfUnderCorePoolSize(command)
private boolean addIfUnderCorePoolSize(Runnable firstTask) {Thread t = null;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (poolSize < corePoolSize && runState == RUNNING) {t = addThread(firstTask);}} finally {mainLock.unlock();}if (t == null) {return false;}t.start();return true;
}
我們重點來看第7行:
private Thread addThread(Runnable firstTask) {Worker w = new Worker(firstTask);Thread t = threadFactory.newThread(w);if (t != null) {w.thread = t;workers.add(w);int nt = ++poolSize;if (nt > largestPoolSize) {largestPoolSize = nt;}}return t;
}
這里將線程封裝成工作線程worker,并放入工作線程組里,worker類的方法run方法:
public void run() {try {Runnable task = firstTask;firstTask = null;while (task != null || (task = getTask()) != null) {runTask(task);task = null;}} finally {workerDone(this);}
}
worker在執(zhí)行完任務(wù)后,還會通過getTask方法循環(huán)獲取工作隊里里的任務(wù)來執(zhí)行。 我們通過一個程序來觀察線程池的工作原理:
3、創(chuàng)建一個線程
public class ThreadPoolTest implements Runnable {@Overridepublic void run() {try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}}
}
4、線程池循環(huán)運行11個線程:
public static void main(String[] args) {LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(5);ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, // 核心線程數(shù)10, // 最大線程數(shù)60, // 空閑線程存活時間TimeUnit.SECONDS, // 時間單位queue // 任務(wù)隊列);for (int i = 0; i < 11; i++) {threadPool.execute(new Thread(new ThreadPoolTest(), "Thread" + i));System.out.println("活躍的線程數(shù): " + threadPool.getPoolSize());if (queue.size() > 0) {System.out.println("----------------隊列阻塞的線程數(shù)" + queue.size());}}threadPool.shutdown();
}
執(zhí)行結(jié)果:
活躍的線程數(shù): 1
活躍的線程數(shù): 2
活躍的線程數(shù): 3
活躍的線程數(shù): 4
活躍的線程數(shù): 5
活躍的線程數(shù): 5
----------------隊列阻塞的線程數(shù)1
活躍的線程數(shù): 5
----------------隊列阻塞的線程數(shù)2
活躍的線程數(shù): 5
----------------隊列阻塞的線程數(shù)3
活躍的線程數(shù): 5
----------------隊列阻塞的線程數(shù)4
活躍的線程數(shù): 5
----------------隊列阻塞的線程數(shù)5
活躍的線程數(shù): 6
----------------隊列阻塞的線程數(shù)5
活躍的線程數(shù): 7
----------------隊列阻塞的線程數(shù)5
活躍的線程數(shù): 8
----------------隊列阻塞的線程數(shù)5
活躍的線程數(shù): 9
----------------隊列阻塞的線程數(shù)5
活躍的線程數(shù): 10
----------------隊列阻塞的線程數(shù)5
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task
Thread[Thread15,5,main] rejected from
java.util.concurrent.ThreadPoolExecutor@232204a1[Running, pool size = 10, active
threads = 10, queued tasks = 5, completed tasks = 0]
at
java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPool
Executor.java:2047)
at
java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at
java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at test.ThreadTest.main(ThreadTest.java:17)
分析結(jié)果
觀察與分析
-
線程池配置概覽:創(chuàng)建的線程池具體配置為:核心線程數(shù)量為5個;最大線程總數(shù)為10個;關(guān)聯(lián)的工作隊列容量為5個任務(wù)。
-
工作隊列監(jiān)控:通過queue.size()方法實時監(jiān)測工作隊列中的任務(wù)數(shù)量,幫助理解線程池的工作狀態(tài)。
-
運行機制解析:
- 初始階段,隨著任務(wù)的提交,線程池會創(chuàng)建核心線程直至達到5個。
- 當核心線程滿載后,新任務(wù)被加入到工作隊列中,直至隊列也達到其容量上限5個。
- 隊列飽和后,線程池會繼續(xù)創(chuàng)建非核心線程,直至線程總數(shù)達到最大限制10個。
- 若線程數(shù)和隊列均達到上限,此時線程池進入飽和狀態(tài),需依據(jù)飽和策略處理后續(xù)提交的任務(wù)。
飽和策略(RejectedExecutionHandler)調(diào)整
當線程池和隊列均達到飽和,即無法接納新任務(wù)時,JDK提供了四種預(yù)設(shè)的飽和策略處理新提交的任務(wù):
- AbortPolicy(默認):直接拋出RejectedExecutionException異常。
- CallerRunsPolicy:將任務(wù)回退給調(diào)用線程直接執(zhí)行。
- DiscardOldestPolicy:丟棄隊列中最舊的任務(wù),嘗試重新提交當前任務(wù)。
- DiscardPolicy:直接丟棄新提交的任務(wù),不拋出異常。
修改示例:現(xiàn)在,我們將上述示例中的飽和策略改為DiscardPolicy,即丟棄新提交的任務(wù)而不拋出異常。
public static void main(String[] args) {// ... 線程池初始化代碼保持不變 ...// 設(shè)置飽和策略為DiscardPolicythreadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());// 任務(wù)提交邏輯保持不變 ...threadPool.shutdown();
}
基于之前的討論,讓我們整合并擴展這部分內(nèi)容,包括修改線程池的飽和策略為DiscardPolicy
并進行相應(yīng)排版:
觀察與分析
-
線程池配置概覽:創(chuàng)建的線程池具體配置為:核心線程數(shù)量為5個;最大線程總數(shù)為10個;關(guān)聯(lián)的工作隊列容量為5個任務(wù)。
-
工作隊列監(jiān)控:通過
queue.size()
方法實時監(jiān)測工作隊列中的任務(wù)數(shù)量,幫助理解線程池的工作狀態(tài)。 -
運行機制解析:
- 初始階段,隨著任務(wù)的提交,線程池會創(chuàng)建核心線程直至達到5個。
- 當核心線程滿載后,新任務(wù)被加入到工作隊列中,直至隊列也達到其容量上限5個。
- 隊列飽和后,線程池會繼續(xù)創(chuàng)建非核心線程,直至線程總數(shù)達到最大限制10個。
- 若線程數(shù)和隊列均達到上限,此時線程池進入飽和狀態(tài),需依據(jù)飽和策略處理后續(xù)提交的任務(wù)。
飽和策略(RejectedExecutionHandler
)調(diào)整
當線程池和隊列均達到飽和,即無法接納新任務(wù)時,JDK提供了四種預(yù)設(shè)的飽和策略處理新提交的任務(wù):
- AbortPolicy(默認):直接拋出
RejectedExecutionException
異常。 - CallerRunsPolicy:將任務(wù)回退給調(diào)用線程直接執(zhí)行。
- DiscardOldestPolicy:丟棄隊列中最舊的任務(wù),嘗試重新提交當前任務(wù)。
- DiscardPolicy:直接丟棄新提交的任務(wù),不拋出異常。
修改示例:現(xiàn)在,我們將上述示例中的飽和策略改為DiscardPolicy
,即丟棄新提交的任務(wù)而不拋出異常。
public static void main(String[] args) {// ... 線程池初始化代碼保持不變 ...// 設(shè)置飽和策略為DiscardPolicythreadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());// 任務(wù)提交邏輯保持不變 ...threadPool.shutdown();
}
通過這樣的調(diào)整,當線程池和隊列達到飽和狀態(tài)后,任何新提交的任務(wù)將被默默地丟棄,這種方式適用于那些可以安全丟棄的任務(wù)場景,避免了因任務(wù)拒絕而引發(fā)的異常中斷,提高了程序的健壯性。