前言
前面我們談完了定時器,單例模式,阻塞隊列等的操作并且做了模擬實現,今天我們再來說一說線程池的操作以及一些鎖策略.
注:本章幾乎均為理論篇,實踐較少.
下面就讓我們開始吧.
線程池
我們知道因為進程的頻繁創建和銷毀,帶來的開銷過大,我們無法接受,所以我們引入了更輕量級的線程,但是其實這里的線程頻繁消耗的話,帶來的開銷也是我們無法接受的,所以我們又想了兩個方案,線程池方案或者是更加輕量級的協程/纖程?
纖程本質上是靠程序員在用戶態進行調度,不是靠內核的調度器來操作,這節省了很多的開銷
舉個例子,如果一個進程中開一千個線程,電腦就卡死了,但是開一千個協程,其實是輕輕松松的
線程池的應用來說就是把線程提前創建好,用完了不要著急銷毀,放進線程池里,以備下次使用,在這個過程中并沒有頻繁的創建和銷毀線程,所以相對來說開銷就小得多了,只是從線程池中取線程來使用,用完了還給線程池即可
tips:內核態和用戶態之間的交互可以理解為,你去銀行辦事情,工作人員讓你去去整一個身份證復印件,你可以讓他幫你整,或者去自己整,他幫你整的話你無法掌控他什么時候幫你整,但是你自己整的話就能確定自己的時間安排
JVM提供的線程池(了解參數即可)
這里我們只要了解最后一個線程池的參數即可,因為幾個方法的參數是重復的,最后一個的參數是最全的.
corePoolSize: 核心線程數? ? ? ? ? ?一個線程池里,最少有多少個線程
maximumPoolSize :最大線程數? ? 一個線程池中,最多有多少個線程
keepAliveTime:線程空閑超過這個時間閾值,就會被銷毀
unit:? ? ? ? ? ? ? ? ?時間單位,取分鐘,秒,小時等等
workQueue:? ? 和定時器一樣,線程池也可以有很多任務,也可以設置為帶有優先級的
threadFactory:? 線程工廠,本質上是給new這個操作封裝了一層,可能同名同參數的構造方法,這樣構成不了重載,我們就想彌補一下這個缺陷,封裝一層構造方法.
handler:拒絕策略(最重要的)
以下是四個Java標準庫中提供的拒絕策略
解決的是在阻塞隊列滿了之后,你還想往里面添加任務,我們以何種方式拒絕
第一種:直接不干了,舊任務和新任務都不干了,直接拋一個異常
第二種:新的任務,由添加任務的線程自己執行
第三種:丟棄一個最老的任務來執行這個任務
第四種:直接丟棄最新的任務
.
但是這個類使用起來還是比較復雜的,所以標準庫還提供了另一個版本來簡化操作,Exectors
都是通過submit添加任務,只不過是構造方法有所差異
所以如果你想實現高度定制化的線程池就使用上面這個很多參數的,不然就可以使用Exectors即可
注:一個線程池中有多少線程不是根據網上說的cpu核心數-1等等這么簡單,其實還是有很多細節之處的,需要我們通過測試來決定,因為有些線程是IO密集型,有些是cpu密集型的,根據情況而定
線程池的簡單實現
使用一個列表來存放隊列,使用一個阻塞隊列來存放任務,其中只有一個submit方法來新建任務
class MyThreadPool{private List<Thread> threadList = new ArrayList<>();private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);public MyThreadPool(int n){for (int i = 0; i < n; i++) {Thread t = new Thread(()->{//TODOwhile(true){try {//隊列為空則會阻塞Runnable runnable = queue.take();//取出任務直接執行就行runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();threadList.add(t);}}public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);} }