1. 程式人生 > >JAVA線程池原理詳解(1)

JAVA線程池原理詳解(1)

err 最大 RKE private queue 分享 ren ++ ant

線程池的優點

1、線程是稀缺資源,使用線程池可以減少創建和銷毀線程的次數,每個工作線程都可以重復使用。

2、可以根據系統的承受能力,調整線程池中工作線程的數量,防止因為消耗過多內存導致服務器崩潰。

線程池的創建

public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
  • corePoolSize:線程池核心線程數量

  • maximumPoolSize:線程池最大線程數量

  • keepAliverTime:當活躍線程數大於核心線程數時,空閑的多余線程最大存活時間

  • unit:存活時間的單位

  • workQueue:存放任務的隊列

  • handler:超出線程範圍和隊列容量的任務的處理程序

線程池的實現原理

提交一個任務到線程池中,線程池的處理流程如下:

1、判斷線程池裏的核心線程是否都在執行任務,如果不是(核心線程空閑或者還有核心線程沒有被創建)則創建一個新的工作線程來執行任務。如果核心線程都在執行任務,則進入下個流程。

2、線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列裏。如果工作隊列滿了,則進入下個流程。

3、判斷線程池裏的線程是否都處於工作狀態,如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。

技術分享圖片

線程池的源碼解讀

1、ThreadPoolExecutor的execute()方法

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
   //如果線程數大於等於基本線程數或者線程創建失敗,將任務加入隊列
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
    //線程池處於運行狀態並且加入隊列成功
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
      //線程池不處於運行狀態或者加入隊列失敗,則創建線程(創建的是非核心線程)
else if (!addIfUnderMaximumPoolSize(command))
       //創建線程失敗,則采取阻塞處理的方式
reject(command); // is shutdown or saturated
}
}

2、創建線程的方法: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在執行完任務後,還會通過getTask方法循環獲取工作隊裏裏的任務來執行。

我們通過一個程序來觀察線程池的工作原理:

1、創建一個線程

public class ThreadPoolTest implements Runnable
{
@Override
public void run()
{
try
{
Thread.sleep(300);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}

2、線程池循環運行16個線程:

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("線程池中活躍的線程數: " + threadPool.getPoolSize());
if (queue.size() > 0)
{
System.out.println("----------------隊列中阻塞的線程數" + queue.size());
}
}
threadPool.shutdown();
}

執行結果:

線程池中活躍的線程數: 1
線程池中活躍的線程數: 2
線程池中活躍的線程數: 3
線程池中活躍的線程數: 4
線程池中活躍的線程數: 5
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數1
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數2
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數4
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數5
線程池中活躍的線程數: 6
----------------隊列中阻塞的線程數5
線程池中活躍的線程數: 7
----------------隊列中阻塞的線程數5
線程池中活躍的線程數: 8
----------------隊列中阻塞的線程數5
線程池中活躍的線程數: 9
----------------隊列中阻塞的線程數5
線程池中活躍的線程數: 10
----------------隊列中阻塞的線程數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)

從結果可以觀察出:

  1. 創建的線程池具體配置為:核心線程數量為5個;全部線程數量為10個;工作隊列的長度為5。

  2. 我們通過queue.size()的方法來獲取工作隊列中的任務數。

  3. 運行原理:

剛開始都是在創建新的線程,達到核心線程數量5個後,新的任務進來後不再創建新的線程,而是將任務加入工作隊列,任務隊列到達上線5個後,新的任務又會創建新的普通線程,直到達到線程池最大的線程數量10個,後面的任務則根據配置的飽和策略來處理。我們這裏沒有具體配置,使用的是默認的配置AbortPolicy:直接拋出異常。

當然,為了達到我需要的效果,上述線程處理的任務都是利用休眠導致線程沒有釋放!!

RejectedExecutionHandler:飽和策略

當隊列和線程池都滿了,說明線程池處於飽和狀態,那麽必須對新提交的任務采用一種特殊的策略來進行處理。這個策略默認配置是AbortPolicy,表示無法處理新的任務而拋出異常。JAVA提供了4中策略:

  • AbortPolicy:直接拋出異常

  • CallerRunsPolicy:只用調用所在的線程運行任務

  • DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。

  • DiscardPolicy:不處理,丟棄掉。

我們現在用第四種策略來處理上面的程序:

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("線程池中活躍的線程數: " + threadPool.getPoolSize());
if (queue.size() > 0)
{
System.out.println("----------------隊列中阻塞的線程數" + queue.size());
}
}
threadPool.shutdown();
}

執行結果

線程池中活躍的線程數: 1
線程池中活躍的線程數: 2
線程池中活躍的線程數: 2
----------------隊列中阻塞的線程數1
線程池中活躍的線程數: 2
----------------隊列中阻塞的線程數2
線程池中活躍的線程數: 2
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 3
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 4
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數3

這裏采用了丟棄策略後,就沒有再拋出異常,而是直接丟棄。在某些重要的場景下,可以采用記錄日誌或者存儲到數據庫中,而不應該直接丟棄。

設置策略有兩種方式:

第一種:

RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);

第二種:

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new ThreadPoolExecut

JAVA線程池原理詳解(1)