生產環境如何使用java線程池

ThreadPoolExecutor誰都會用,但是如何用的好、用的對,可能就仁者見仁、智者見智~

吹之前,還是把ThreadPoolExecutor簡單介紹一下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)

參數介紹:

  • corePoolSize :線程池的核心池大小,在創建線程池之後,線程池默認沒有任何線程。當有任務過來的時候纔會去創建創建線程執行任務。換個說法,線程池創建之後,線程池中的線程數爲0,當任務過來就會創建一個線程去執行,直到線程數達到corePoolSize 之後,就會被到達的任務放在隊列中(說明:除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建corePoolSize個線程或者一個線程)。
  • maximumPoolSize :線程池允許的最大線程數,表示最大能創建多少個線程。maximumPoolSize>=corePoolSize。
  • keepAliveTime :表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用,直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,如果一個線程空閒的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數爲0;
  • workQueue :一個阻塞隊列,用來存儲等待執行的任務,當線程池中的線程數超過它的corePoolSize的時候,線程會進入阻塞隊列進行阻塞等待。通過workQueue,線程池實現了阻塞功能
  • threadFactory :線程工廠,用來創建線程。
  • handler :表示當拒絕處理任務時的策略。

現在迴歸主題,這些參數怎麼設置纔算合理?越多越好?肯定不是,太多甚至會導致災難;越少越好?也不是;

其實這是要根據具體的業務場景來判斷的,分別從任務的角度、空間時間的角度去吹:

如何設置合理的線程數

1、任務一般分爲:CPU密集型、IO密集型、混合型,對於不同類型的任務需要分配不同大小的線程池

  • CPU密集型:儘量使用較小的線程池,一般Cpu核心數+1;因爲CPU密集型任務CPU的使用率很高,若開過多的線程,只能增加線程上下文的切換次數,帶來額外的開銷
  • IO密集型:方法一:可以使用較大的線程池,一般CPU核心數 * 2;IO密集型CPU使用率不高,可以讓CPU等待IO的時候處理別的任務,充分利用cpu時間;方法二:線程等待時間所佔比例越高,需要越多線程。線程CPU時間所佔比例越高,需要越少線程。舉個例子:比如平均每個線程CPU運行時間爲0.5s,而線程等待時間(非CPU運行時間,比如IO)爲1.5s,CPU核心數爲8,那麼根據上面這個公式估算得到:((0.5+1.5)/0.5)*8=32。這個公式進一步轉化爲:
    最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目

如何設置合理的隊列大小

2、時間空間的限制

  • 基於空間 :比如隊列可以佔用10M內存,每個請求大小10K ,那麼workQueue隊列長度爲1000合適
  • 基於時間 :對於單個線程,如果請求超時時間爲1s,單個請求平均處理時間10ms,那麼隊列長度爲100合適

不成文強制規定

1、線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors返回的線程池對象弊端如下:

  • FixedThreadPool和SingleThreadPool:允許的請求隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
  • CacheThreadPool和ScheduledThreadPool:允許創建線程數量爲Integer.MAX_VALUE,可能會創建大量線程,從而導致OOM。

2、給線程起個名字

線上不出bug,隨便起啥名字你都無所謂,但如果產生bug了,大量的日誌中,合理的線程名對於問題的定位還是很有幫助的。

3、重視線程中拋出的異常

爲啥要重視,看個例子:

package com.thread.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class LostThreadException  implements Runnable{
    @Override
    public void run() {
        throw new RuntimeException();
    }
    //現象:控制檯打印出異常信息,並運行一段時間後才停止
    public static void main(String[] args){
        //就算把線程的執行語句放到try-catch塊中也無濟於事
        try{
            System.out.println("Begin executor.....!");
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new LostThreadException());
            System.out.println("Finish executor.....!");
        }catch(RuntimeException e){
            System.out.println("Exception happened!");
        }
    }
}

運行結果:

Begin executor.....!
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
	at com.thread.test.LostThreadException.run(LostThreadException.java:14)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Finish executor.....!

我們發現System.out.println("Exception happened!");並沒有打印出來,即:異常在main線程中沒有catch到;這就是你爲什麼要自己主動去處理線程中拋出的異常;開個玩笑,如果你在生產中產生了上述的現象,唯一的線索是:"pool-1-thread-1“,這是線程名,自動生成的名稱,是不是很崩潰,如果你有良好的習慣,按照2中所說,自定義線程名,最起碼你知道這個異常是哪個類型的線程池中的線程拋出來的,否則只能望洋興嘆,愁眉苦臉~

關於這種異常處理,網上有很多講解,實現方式感覺不是太優雅,大家可以自己去體會~

 

 

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