JAVA線程池原理實(shí)例詳解
本文實(shí)例講述了JAVA線程池原理。分享給大家供大家參考,具體如下:
線程池的優(yōu)點(diǎn)
1、線程是稀缺資源,使用線程池可以減少創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以重復(fù)使用。
2、可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線程的數(shù)量,防止因?yàn)橄倪^多內(nèi)存導(dǎo)致服務(wù)器崩潰。
線程池的創(chuàng)建
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
- corePoolSize:線程池核心線程數(shù)量
- maximumPoolSize:線程池最大線程數(shù)量
- keepAliverTime:當(dāng)活躍線程數(shù)大于核心線程數(shù)時(shí),空閑的多余線程最大存活時(shí)間
- unit:存活時(shí)間的單位
- workQueue:存放任務(wù)的隊(duì)列
- handler:超出線程范圍和隊(duì)列容量的任務(wù)的處理程序
線程池的實(shí)現(xiàn)原理
提交一個(gè)任務(wù)到線程池中,線程池的處理流程如下:
1、判斷線程池里的核心線程是否都在執(zhí)行任務(wù),如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)建)則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)。如果核心線程都在執(zhí)行任務(wù),則進(jìn)入下個(gè)流程。
2、線程池判斷工作隊(duì)列是否已滿,如果工作隊(duì)列沒有滿,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里。如果工作隊(duì)列滿了,則進(jìn)入下個(gè)流程。
3、判斷線程池里的線程是否都處于工作狀態(tài),如果沒有,則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)。如果已經(jīng)滿了,則交給飽和策略來處理這個(gè)任務(wù)。

線程池的源碼解讀
1、ThreadPoolExecutor的execute()方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException(); //如果線程數(shù)大于等于基本線程數(shù)或者線程創(chuàng)建失敗,將任務(wù)加入隊(duì)列
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {//線程池處于運(yùn)行狀態(tài)并且加入隊(duì)列成功
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}//線程池不處于運(yùn)行狀態(tài)或者加入隊(duì)列失敗,則創(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;
}
我們重點(diǎn)來看第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ù)后,還會(huì)通過getTask方法循環(huán)獲取工作隊(duì)里里的任務(wù)來執(zhí)行。
我們通過一個(gè)程序來觀察線程池的工作原理:
1、創(chuàng)建一個(gè)線程
public class ThreadPoolTest implements Runnable
{
@Override
public void run()
{
try
{
Thread.sleep(300);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
2、線程池循環(huán)運(yùn)行16個(gè)線程:
public static void main(String[] args)
{
LinkedBlockingQueue<Runnable> queue =
new LinkedBlockingQueue<Runnable>(5);
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, queue);
for (int i = 0; i < 16 ; i++)
{
threadPool.execute(
new Thread(new ThreadPoolTest(), "Thread".concat(i + "")));
System.out.println("線程池中活躍的線程數(shù): " + threadPool.getPoolSize());
if (queue.size() > 0)
{
System.out.println("----------------隊(duì)列中阻塞的線程數(shù)" + queue.size());
}
}
threadPool.shutdown();
}
執(zhí)行結(jié)果:
線程池中活躍的線程數(shù): 1
線程池中活躍的線程數(shù): 2
線程池中活躍的線程數(shù): 3
線程池中活躍的線程數(shù): 4
線程池中活躍的線程數(shù): 5
線程池中活躍的線程數(shù): 5
----------------隊(duì)列中阻塞的線程數(shù)1
線程池中活躍的線程數(shù): 5
----------------隊(duì)列中阻塞的線程數(shù)2
線程池中活躍的線程數(shù): 5
----------------隊(duì)列中阻塞的線程數(shù)3
線程池中活躍的線程數(shù): 5
----------------隊(duì)列中阻塞的線程數(shù)4
線程池中活躍的線程數(shù): 5
----------------隊(duì)列中阻塞的線程數(shù)5
線程池中活躍的線程數(shù): 6
----------------隊(duì)列中阻塞的線程數(shù)5
線程池中活躍的線程數(shù): 7
----------------隊(duì)列中阻塞的線程數(shù)5
線程池中活躍的線程數(shù): 8
----------------隊(duì)列中阻塞的線程數(shù)5
線程池中活躍的線程數(shù): 9
----------------隊(duì)列中阻塞的線程數(shù)5
線程池中活躍的線程數(shù): 10
----------------隊(duì)列中阻塞的線程數(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(ThreadPoolExecutor.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é)果可以觀察出:
1、創(chuàng)建的線程池具體配置為:核心線程數(shù)量為5個(gè);全部線程數(shù)量為10個(gè);工作隊(duì)列的長度為5。
2、我們通過queue.size()的方法來獲取工作隊(duì)列中的任務(wù)數(shù)。
3、運(yùn)行原理:
剛開始都是在創(chuàng)建新的線程,達(dá)到核心線程數(shù)量5個(gè)后,新的任務(wù)進(jìn)來后不再創(chuàng)建新的線程,而是將任務(wù)加入工作隊(duì)列,任務(wù)隊(duì)列到達(dá)上線5個(gè)后,新的任務(wù)又會(huì)創(chuàng)建新的普通線程,直到達(dá)到線程池最大的線程數(shù)量10個(gè),后面的任務(wù)則根據(jù)配置的飽和策略來處理。我們這里沒有具體配置,使用的是默認(rèn)的配置AbortPolicy:直接拋出異常。
當(dāng)然,為了達(dá)到我需要的效果,上述線程處理的任務(wù)都是利用休眠導(dǎo)致線程沒有釋放?。?!
RejectedExecutionHandler:飽和策略
當(dāng)隊(duì)列和線程池都滿了,說明線程池處于飽和狀態(tài),那么必須對新提交的任務(wù)采用一種特殊的策略來進(jìn)行處理。這個(gè)策略默認(rèn)配置是AbortPolicy,表示無法處理新的任務(wù)而拋出異常。JAVA提供了4中策略:
1、AbortPolicy:直接拋出異常
2、CallerRunsPolicy:只用調(diào)用所在的線程運(yùn)行任務(wù)
3、DiscardOldestPolicy:丟棄隊(duì)列里最近的一個(gè)任務(wù),并執(zhí)行當(dāng)前任務(wù)。
4、DiscardPolicy:不處理,丟棄掉。
我們現(xiàn)在用第四種策略來處理上面的程序:
public static void main(String[] args)
{
LinkedBlockingQueue<Runnable> queue =
new LinkedBlockingQueue<Runnable>(3);
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
for (int i = 0; i < 9 ; i++)
{
threadPool.execute(
new Thread(new ThreadPoolTest(), "Thread".concat(i + "")));
System.out.println("線程池中活躍的線程數(shù): " + threadPool.getPoolSize());
if (queue.size() > 0)
{
System.out.println("----------------隊(duì)列中阻塞的線程數(shù)" + queue.size());
}
}
threadPool.shutdown();
}
執(zhí)行結(jié)果:
線程池中活躍的線程數(shù): 1
線程池中活躍的線程數(shù): 2
線程池中活躍的線程數(shù): 2
----------------隊(duì)列中阻塞的線程數(shù)1
線程池中活躍的線程數(shù): 2
----------------隊(duì)列中阻塞的線程數(shù)2
線程池中活躍的線程數(shù): 2
----------------隊(duì)列中阻塞的線程數(shù)3
線程池中活躍的線程數(shù): 3
----------------隊(duì)列中阻塞的線程數(shù)3
線程池中活躍的線程數(shù): 4
----------------隊(duì)列中阻塞的線程數(shù)3
線程池中活躍的線程數(shù): 5
----------------隊(duì)列中阻塞的線程數(shù)3
線程池中活躍的線程數(shù): 5
----------------隊(duì)列中阻塞的線程數(shù)3
這里采用了丟棄策略后,就沒有再拋出異常,而是直接丟棄。在某些重要的場景下,可以采用記錄日志或者存儲(chǔ)到數(shù)據(jù)庫中,而不應(yīng)該直接丟棄。
設(shè)置策略有兩種方式:
1、
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy(); ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
2、
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue); threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
更多java相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Java進(jìn)程與線程操作技巧總結(jié)》、《Java數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Java操作DOM節(jié)點(diǎn)技巧總結(jié)》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總》
希望本文所述對大家java程序設(shè)計(jì)有所幫助。
相關(guān)文章
java abstract class interface之間的區(qū)別介紹
含有abstract修飾符的class即為抽象類,abstract 類不能創(chuàng)建的實(shí)例對象,abstract class類中定義抽象方法必須在具體(Concrete)子類中實(shí)現(xiàn),所以,不能有抽象構(gòu)造方法或抽象靜態(tài)方法2012-11-11
Java靜態(tài)代理和動(dòng)態(tài)代理的深入講解
這篇文章主要給大家介紹了關(guān)于Java靜態(tài)代理和動(dòng)態(tài)代理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
使用Java編寫導(dǎo)出不確定行數(shù)列數(shù)數(shù)據(jù)的工具類
這篇文章主要為大家詳細(xì)介紹了如何使用Java編寫導(dǎo)出不確定行數(shù)列數(shù)數(shù)據(jù)的工具類,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
SpringMvc根據(jù)返回值類型不同處理響應(yīng)的方法
這篇文章主要介紹了SpringMvc根據(jù)返回值類型不同處理響應(yīng),我們可以通過控制器方法的返回值設(shè)置跳轉(zhuǎn)的視圖,控制器支持如void,String,ModelAndView類型,需要的朋友可以參考下2023-09-09
springboot向elk寫日志實(shí)現(xiàn)過程
這篇文章主要介紹了springboot向elk寫日志實(shí)現(xiàn)過程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10

