JAVA線程池原理詳解一

線程池的優勢

一、線程是稀缺資源,使用線程池能夠減小建立和銷燬線程的次數,每一個工做線程均可以重複使用。java

二、能夠根據系統的承受能力,調整線程池中工做線程的數量,防止由於消耗過多內存致使服務器崩潰。數據庫

線程池的建立

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

corePoolSize:線程池核心線程數量服務器

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

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

unit:存活時間的單位spa

workQueue:存聽任務的隊列線程

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

線程池的實現原理

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

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

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

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

  這裏寫圖片描述

線程池的源碼解讀

一、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 }

二、建立線程的方法: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 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 }

二、線程池循環運行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)

從結果能夠觀察出:

一、建立的線程池具體配置爲:核心線程數量爲5個;所有線程數量爲10個;工做隊列的長度爲5。

二、咱們經過queue.size()的方法來獲取工做隊列中的任務數。

三、運行原理:

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

  固然,爲了達到我須要的效果,上述線程處理的任務都是利用休眠致使線程沒有釋放!!!

RejectedExecutionHandler:飽和策略

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

一、AbortPolicy:直接拋出異常

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

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

四、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

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

設置策略有兩種方式:

一、

 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 ThreadPoolExecutor.AbortPolicy());
相關文章
相關標籤/搜索