線程池原理

線程池的優點

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

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

線程池的創建

1 public ThreadPoolExecutor(int corePoolSize,
2                               int maximumPoolSize,
3                               long keepAliveTime,
4                               TimeUnit unit,
5                               BlockingQueue<Runnable> workQueue,
6                               RejectedExecutionHandler handler) 

corePoolSize:線程池核心線程數量

maximumPoolSize:線程池最大線程數量

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

unit:存活時間的單位

workQueue:存放任務的隊列

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

線程池的實現原理

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

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

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

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

  這裏寫圖片描述

線程池的源碼解讀

1、ThreadPoolExecutor的execute()方法

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

2、創建線程的方法:addIfUnderCorePoolSize(command)

複製代碼
 1 private boolean addIfUnderCorePoolSize(Runnable firstTask) {
 2         Thread t = null;
 3         final ReentrantLock mainLock = this.mainLock;
 4         mainLock.lock();
 5         try {
 6             if (poolSize < corePoolSize && runState == RUNNING)
 7                 t = addThread(firstTask);
 8         } finally {
 9             mainLock.unlock();
10         }
11         if (t == null)
12             return false;
13         t.start();
14         return true;
15     }
複製代碼

我們重點來看第7行:

複製代碼
 1 private Thread addThread(Runnable firstTask) {
 2         Worker w = new Worker(firstTask);
 3         Thread t = threadFactory.newThread(w);
 4         if (t != null) {
 5             w.thread = t;
 6             workers.add(w);
 7             int nt = ++poolSize;
 8             if (nt > largestPoolSize)
 9                 largestPoolSize = nt;
10         }
11         return t;
12     }
複製代碼

這裏將線程封裝成工作線程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、創建一個線程

複製代碼
 1 public class ThreadPoolTest implements Runnable
 2 {
 3     @Override
 4     public void run()
 5     {
 6         try
 7         {
 8             Thread.sleep(300);
 9         }
10         catch (InterruptedException e)
11         {
12             e.printStackTrace();
13         }
14     }
15 }
複製代碼

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

複製代碼
 1 public static void main(String[] args)
 2     {
 3         LinkedBlockingQueue<Runnable> queue =
 4             new LinkedBlockingQueue<Runnable>(5);
 5         ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, queue);
 6         for (int i = 0; i < 16 ; i++)
 7         {
 8             threadPool.execute(
 9                 new Thread(new ThreadPoolTest(), "Thread".concat(i + "")));
10             System.out.println("線程池中活躍的線程數: " + threadPool.getPoolSize());
11             if (queue.size() > 0)
12             {
13                 System.out.println("----------------隊列中阻塞的線程數" + queue.size());
14             }
15         }
16         threadPool.shutdown();
17     }
複製代碼

執行結果:

複製代碼
線程池中活躍的線程數: 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中策略:

1、AbortPolicy:直接拋出異常

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

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

4、DiscardPolicy:不處理,丟棄掉。

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

 

複製代碼
 1 public static void main(String[] args)
 2     {
 3         LinkedBlockingQueue<Runnable> queue =
 4             new LinkedBlockingQueue<Runnable>(3);
 5         RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
 6 
 7         ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
 8         for (int i = 0; i < 9 ; i++)
 9         {
10             threadPool.execute(
11                 new Thread(new ThreadPoolTest(), "Thread".concat(i + "")));
12             System.out.println("線程池中活躍的線程數: " + threadPool.getPoolSize());
13             if (queue.size() > 0)
14             {
15                 System.out.println("----------------隊列中阻塞的線程數" + queue.size());
16             }
17         }
18         threadPool.shutdown();
19     }
複製代碼

 

執行結果:

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

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

設置策略有兩種方式:

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());

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章