線程池
線程誕生的意義:因為進程的創建/銷毀,太重量了(比較慢)
但如果近一步提高創建/銷毀的頻率,線程的開銷也不容忽視。
有兩種方法可以提高效率:
1.協程(輕量級線程):相對于線程,把系統調度的過程省略了。
使用協程更多的是go和python
不知道協程能提升多少,防止出現bug,java一般使用線程
2.線程池:幫線程兜底,不至于很慢(就像現實生活中的“海王”)
(內存池、線程池、進程池含義類似)
在使用第一個線程的時候,提前把2,3,4,5(其余)線程創建好;
后續如果想要使用新的線程,不必重新創建,直接調用即可,沒有真正的頻繁創建銷毀,只是從線程池里面取線程使用,等使用完了在還給線程池,這樣的話創建線程的開銷就減少了。
調用線程比創建新線程效率更高
1.調用線程是純粹“用戶態”的操作
2.創建新的線程 是需要“用戶態+內核態”共同完成的?
內核態和用戶態:
一段程序在系統內核執行 -> 內核態;
反之,用戶態。
官方文檔
在java標準庫里面,ThreadPoolExecutor類表示線程池。
ThreadPoolExecutor類的構造方法
參數具體含義:
1. 線程數:int?corePoolSize, int?maximumPoolSize(核心線程數和最大線程數)
corePoolSize -> 核心線程數 -> 正式員工數
maximumPoolSize -> 最大線程數 -> 正式員工數 + 實習員工數
- 線程池允許創建的最大線程數量。
- 當工作隊列滿了,并且當前線程數小于最大線程數時,線程池會創建新的線程來處理任務
eg:
當核心線程處于忙碌中且有大量新的任務需要處理時,會創建實習員工線程,來幫核心線程處理;當任務數量變少時且持續一段時間,核心線程可以閑著(摸魚),但實習員工線程全部銷毀,提高了效率且節省了系統開銷。
2. long?keepAliveTime, TimeUnit?unit(保持存活時間和存活時間的單位)
keepAliveTime:當線程池中的線程數量超過核心線程數時,多余的空閑線程在等待任務的時間超過這個值后,就會被銷毀
unit:hour、min、s、ms
3.?BlockingQueue<Runnable>?workQueue
用來存放線程池中的任務的隊列,使用Runnable來描述任務主體。
根據需要設置:
需要優先級:設置PriorityBlockingQueue
不需要優先級,并且任務數目相對恒定:使用ArrayBlockingQueue
不需要優先級,并且任務數目變動大:使用LinkedBlockingQueue
4.ThreadFactory?threadFactory(線程工廠)
?通過這個工廠類創建線程(Thread)對象,工廠類里面有方法封裝了new Thread的操作,同時給Thread設置了一些屬性,我們想要創建線程的時候可以直接使用工廠類的方法創建。
eg:
通過靜態方法來封裝new操作,在這個靜態方法設置不同的屬性,構造對象的過程,就稱為工廠模式。
5.RejectedExecutionHandler?handler(拒絕策略)
當workQueue滿了,并且線程池中的線程數量已經達到最大線程數時,新的任務將會被拒絕線程池會采用拒絕策略。
Executors類:工廠類
ThreadPoolExecutor使用較復雜,所以通過封裝,創建了工廠類Executors;
通過這個類可以創建出不同的線程池對象,內部已經把ThreadPoolExecutor創建好并設置好參數。
eg;使用newFixedThreadPool(4)創建了固定線程數目為4的線程池,往里面添加任務。
線程池的執行流程
線程池如何設置線程數目
我們將任務分為CPU密集型和I/O密集型
CPU密集型:在cpu上執行,當線程數超過CPU核心數時,線程需要競爭CPU時間片,這會帶來額外的開銷,降低效率。
所以線程數目不應超過N(CPU核心數)
I/O密集型:涉及大量I/O操作,大部分都是在等I/O完成,不是執行CPU,(當一個線程在執行I/O操作時,它會阻塞,釋放CPU。此時,CPU可以去執行其他線程的任務)。
I/O密集型任務的線程數可以設置得比CPU核心數多,超過N。
在實際應用中,任務往往是CPU密集型和I/O密集型的混合體。更好的方法是通過實驗/測試的方法,找出合適的線程數目。