一、💛
線程池的基本介紹
內存池,進程池,連接池,常量池,這些池子概念上都是一樣的~~
如果我們需要頻繁的創建銷毀線程,此時創建銷毀的成本就不能忽視了,因此就可以使用線程池。
提前創建好一波線程,后續需要使用線程,就直接從池子里面拿一個即可,當線程不再使用,就放回池子里面。(本來是需要創建線程/銷毀線程,現在是從池子里面獲取到現成的線程,并且把線程歸還到池子里面
那么為啥從池子里面拿就比系統里面創建線程更加高效呢?
不用線程池:如果是系統這里創建線程,需要調用系統API,進一步的由操作系統內核完成,完成線程的創建過程(內核是給所有進程提供服務的,這樣你想干的事情,就需要等一等,等多長時間我們是未知的,是不可以控制的)
使用線程池:上述的內核中進行的操作都是提前做好了的,現在的取線程過程,純粹的是用戶使用代碼完成的(純用戶態)-是可控制的。
二、💙
工廠模式:去生產的功能(字面意思),用于生產對象,一般情況下我們創建對象都是new,通過構造方法,但是構造方法有時候存在巨大的缺陷(構造方法是固定就是類名,有的類需要使用多種不同構造方式->(方法重載僅要求參數的個數和類型有區別)
~比如說:表示坐標->這種無法構成重載
public class Circle {public Circle(double x,double y){ //笛卡爾坐標};public Circle(double r,double a){ //極坐標}; } 所以上面的代碼也不對,構不成方法重載
使用工廠模式來解決上述問題,不使用構造方法來,(我剛開始也在想為什么是靜態,直到我自己去試一下,明白了也就是說不用構造方法創建對象,假如不是靜態方法,那么該怎么調用他呢,不是靜態的,可是只能用對象.方法才可以調用)用普通方法來構造對象,這樣方法就可以任意的了,普通方法內部去new對象~由于普通方法的目的是創建對象,然后調用方法來設置屬性,所以方法一般都是靜態的。(類名.方法)
三、💜?
//Executors:工廠類,后面的是工廠方法。這句話是創建一個固定線程數量的線程池
//線程池對象:ExecutorService serviceExecutorService service= Executors.newFixedThreadPool(4);
//創建一個線程數組,動態變化的線程池。ExecutorService service2= Executors.newCachedThreadPool();
//包含單個線程(比原生創建API更簡單一些指Thread)ExecutorService service3= Executors.newSingleThreadExecutor();
//類似于定時器效果,添加一些任務,執行,被執行的時候,不是只有有一個掃描線程來執行,可能是有多個共同執行ExecutorService service4= Executors.newScheduledThreadPool();}
?
四、??
面試題:談談java庫中的線程池構成方法的參數和含義
這個方法最復雜,而且別的參數這個參數都有,所以就只解釋這個方法就行。
int corePoolSize:核心線程數
int maximunPoolSize:最大線程數
ThreadPoolExcutor:里面的線程個數,并
非固定不變的,會根據當前任務的情況動態變化(自適應)
corePoolSize:至少要這些線程,哪怕你的線程都沒任務也要這些個線程(如同公司里面的正式員工)
maximumPoolSize:最多不超過這些線程,哪怕干冒煙了,也不能比這個更多了(正式員工+實習生)
long keepAliveTime, TimeUnit unit:實習生線程,空閑時間超過指定閾值(允許實習生摸魚的最大時間),就可以銷毀了
BlockingQueue<Runnable>workQueue:線程池內部有很多很多,任務可以使用阻塞隊列管理,線程池可以內置阻塞隊列,也可以手動一個。
RejectedExecutionHandler ? handler:線程池考的重點,拒絕方式/拒絕策略,線程池有一個阻塞隊列,當隊列滿了,繼續加任務如何處理。
1.ThreadPoolExecutors.AbortPolicy:直接拋出異常,線程池就不干活了,(小王喊我打球,我在學習,我直接崩潰了,我哇哇大哭)
2.ThreadPoolExecutor.callerRunsPolicy:誰說添加這個任務的線程,誰就去執行這個任務
,我會直接說:我沒空,自己投去吧)
3.ThreadPoolExecutor.DiscardPolicy:把新的任務丟棄,(不打球了,我接著學習)
4.ThreadPoolExecutor.DiscardPolicy:丟棄最早的任務,執行新的任務(放棄學習,去打球)
有的線程公司會推薦使用這個。
?
五、💚?
具體實現一個線程池
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;class MyThreadPool {private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();//通過這個方法把任務添加到線程池中public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}//n表示一個線程池里面有幾個字段,創建了一個固定數量的線程池public MyThreadPool(int n) throws InterruptedException {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {Runnable runnable = null;try {runnable = queue.take(); //從隊列中提取這個任務} catch (InterruptedException e) {e.printStackTrace();}runnable.run(); //運行這個任務}});t.start(); //開啟這個線程}}
}
public class Demo4 {public static void main(String[] args) throws InterruptedException {//獲取當前引用實例MyThreadPool myThreadPool = new MyThreadPool(4);for (int i = 0; i < 1000; i++) { //循環很關鍵哈,這個循環是添加1000次任務,但是假如你放到run里面,就會變成一個任務,至少內容是執行1000遍,線程是執行多個任務,但是不能一個任務還分擔myThreadPool.submit(new Runnable() { //(安排任務,安排一個任務)@Override //這個任務進行的工作public void run() {System.out.println(Thread.currentThread().getName() + " love");}});}}
}
面試問題2號:創建線程池的時候,線程個數怎么數的:
網路上查資料很多:假設cpu邏輯數是N,線程的個數:N,N+1,2N···
準確的說都不準確,因為不同的項目要做的工作是不同的:
cpu密集型線程工作:全是運算大部分工作在cpu上完成,cpu給他安排核心,才能概括,假如cpu N個核心,線程數量最好也是為N,如果多了,線程也只能是排隊等待,沒有新的進展
Io密集型線程工作:涉及大量等待時間,等待的過程,不要cpu,所以這里線程多,也會給cpu造成負擔,cpu16核,整個32個線程,不犯毛病(不耗cpu,甚至cpu占用很低)———
實際上,一部分cpu密集,一部分Io密集,是我們工作中的常態,此時一個線程多少在cpu上執行,多少等待IO,說不好,更好的做法:自己去性能測試一下,找到性能和開銷比較均衡的數值。
六、💔?
多線程進階開啟:
常見的鎖策略
如果工作中,真正要實現一把鎖,需要理解鎖策略
1.樂觀鎖VS悲觀鎖
樂觀鎖:預測,不太會出現鎖沖突的情況
悲觀鎖:預測,這個場景非常容易鎖沖突
2.重量級鎖VS輕量級鎖
重量級鎖:加鎖開銷比較大,花的時間多,占有系統資源,一個悲觀鎖,很可能是重量級鎖
輕量級鎖:花的時間少,占有資源少,加鎖的開銷比較小的,很可能是樂觀鎖
悲觀,樂觀是加鎖之前堆沖突概率的預測,決定工作的多少,重量,輕量,是加鎖后,考慮實際的鎖開銷
3.自旋鎖VS刮起等待鎖
自旋鎖是輕量級的一種典型實現,在用戶態通過自旋的方式(while循環),實現類似加鎖的操作(一直在瘋狂的舔,這種鎖會耗一定的cpu,但是是最快速度拿到鎖的)
掛起等待鎖:通過內核態,借助系統提供的鎖機制,當出現鎖沖突,會牽扯到內核對線程的調度,是沖突的線程出現掛起(阻塞等待)重量級鎖的一種典型體現,發現鎖被占用后,自己該干啥干啥,偶爾聽到了消息,又去找這個鎖,耗費cpu少,但無法第一時間拿到鎖(小擺爛)
4.讀寫鎖VS互斥鎖
讀寫鎖:把讀操作,寫操作分開了
假如兩個線程 一個讀加鎖,另一個還是讀加鎖,那么兩個不會有鎖競爭(目的:就是把這種情況處理,這樣多線程的效率會更高)
一個讀,一個寫,兩個都是寫都會有鎖競爭,但是兩個讀沒事,在開發中讀操作會比寫操作更加頻繁
互斥鎖:寫了就不能讀,讀了就不可以寫,
5.公平鎖VS非公平鎖
公平鎖是遵循先來后到這個規則的
非公平鎖相當于超市促銷,都來搶位置,不遵守順序
操作系統自帶鎖(pthread-mutex)是非公平鎖,要實現公平鎖,就需要一些額外的數據結構來支持(比如需要有辦法記錄每個線程的阻塞等待時間)