成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

我會(huì)手動(dòng)創(chuàng)建線程,為什么讓我使用線程池?

開發(fā) 后端
在這篇文章中我們明確說(shuō)明,new Thread() 在操作系統(tǒng)層面并沒有創(chuàng)建新的線程,這是編程語(yǔ)言特有的。真正轉(zhuǎn)換為操作系統(tǒng)層面創(chuàng)建一個(gè)線程,還要調(diào)用操作系統(tǒng)內(nèi)核的API,然后操作系統(tǒng)要為該線程分配一系列的資源。

好看請(qǐng)贊,養(yǎng)成習(xí)慣

  •   你有一個(gè)思想,我有一個(gè)思想,我們交換后,一個(gè)人就有兩個(gè)思想
  •  If you can NOT explain it simply, you do NOT understand it well enough

上一篇文章 面試問我,創(chuàng)建多少個(gè)線程合適?我該怎么說(shuō) 從定性到定量的分析了如何創(chuàng)建正確個(gè)數(shù)的線程來(lái)最大化利用系統(tǒng)資源(其實(shí)就是幾道小學(xué)數(shù)學(xué)題)。通常來(lái)講,有了個(gè)這個(gè)知識(shí)點(diǎn)傍身,按需手動(dòng)創(chuàng)建相應(yīng)個(gè)數(shù)的線程就好

但是現(xiàn)實(shí)中,你也許聽過或者被要求:

盡量避免手動(dòng)創(chuàng)建線程,應(yīng)使用線程池統(tǒng)一管理線程

為什么會(huì)有這樣的要求?背后的道理又是怎樣的呢?順著這個(gè)經(jīng)驗(yàn)理論來(lái)推斷,那肯定是手動(dòng)創(chuàng)建線程有缺點(diǎn)

手動(dòng)創(chuàng)建線程有什么缺點(diǎn)?

  1.  不受控風(fēng)險(xiǎn)
  2.  頻繁創(chuàng)建開銷大

不受控風(fēng)險(xiǎn)

這個(gè)缺點(diǎn),相信你也可以說(shuō)出一二

系統(tǒng)資源有限,每個(gè)人針對(duì)不同業(yè)務(wù)都可以手動(dòng)創(chuàng)建線程,并且創(chuàng)建標(biāo)準(zhǔn)不一樣(比如線程沒有名字)。當(dāng)系統(tǒng)運(yùn)行起來(lái),所有線程都在瘋狂搶占資源,無(wú)組織無(wú)紀(jì)律,混亂場(chǎng)面可想而知(出現(xiàn)問題,自然也就不可能輕易的發(fā)現(xiàn)和解決)

[[324373]]

如果有位神奇的小伙伴,為每個(gè)請(qǐng)求都創(chuàng)建一個(gè)線程,當(dāng)大量請(qǐng)求鋪面而來(lái)的時(shí)候,這好比一個(gè)正規(guī)木馬程序,內(nèi)存被無(wú)情榨干耗盡(你無(wú)情,你冷酷,你無(wú)理取鬧)

[[324374]]

另外,過多的線程自然也會(huì)引起上下文切換的開銷

總的來(lái)說(shuō),不受控風(fēng)險(xiǎn)很大

頻繁創(chuàng)建開銷大

面試問: 頻繁手動(dòng)創(chuàng)建線程有什么問題?

答: 開銷大

這貌似是一個(gè)不假思索就可以回答出來(lái)的正確答案。那我要繼續(xù)問了

面試官: 創(chuàng)建一個(gè)線程干了什么就開銷大了?和我們創(chuàng)建一個(gè)普通 Java 對(duì)象有什么差別?

答: ... 嗯...啊

按照常規(guī)理解 new Thread() 創(chuàng)建一個(gè)線程和 new Object() 沒有什么差別。Java中萬(wàn)物接對(duì)象,因?yàn)?Thread 的老祖宗也是 Object

如果你真是這么理解的,說(shuō)明你對(duì)線程的生命周期還不是很理解,請(qǐng)回看之前的 Java線程生命周期這樣理解挺簡(jiǎn)單的

在這篇文章中我們明確說(shuō)明,new Thread() 在操作系統(tǒng)層面并沒有創(chuàng)建新的線程,這是編程語(yǔ)言特有的。真正轉(zhuǎn)換為操作系統(tǒng)層面創(chuàng)建一個(gè)線程,還要調(diào)用操作系統(tǒng)內(nèi)核的API,然后操作系統(tǒng)要為該線程分配一系列的資源

廢話不多說(shuō),我們將二者做個(gè)對(duì)比:

new Object() 過程

 

  1. Object obj = new Object(); 

當(dāng)我需要【對(duì)象】時(shí),我就會(huì)給自己 new 一個(gè)(不知你是否和我一樣),這個(gè)過程你應(yīng)該很熟悉了:

  1.  分配一塊內(nèi)存 M
  2.  在內(nèi)存 M 上初始化該對(duì)象
  3.  將內(nèi)存 M 的地址賦值給引用變量 obj

就是這么簡(jiǎn)單

創(chuàng)建一個(gè)線程的過程

上面已經(jīng)提到了,創(chuàng)建一個(gè)線程還要調(diào)用操作系統(tǒng)內(nèi)核API。為了更好的理解創(chuàng)建并啟動(dòng)一個(gè)線程的開銷,我們需要看看 JVM 在背后幫我們做了哪些事情:

  1.  它為一個(gè)線程棧分配內(nèi)存,該棧為每個(gè)線程方法調(diào)用保存一個(gè)棧幀
  2.  每一棧幀由一個(gè)局部變量數(shù)組、返回值、操作數(shù)堆棧和常量池組成
  3.  一些支持本機(jī)方法的 jvm 也會(huì)分配一個(gè)本機(jī)堆棧
  4.  每個(gè)線程獲得一個(gè)程序計(jì)數(shù)器,告訴它當(dāng)前處理器執(zhí)行的指令是什么
  5.  系統(tǒng)創(chuàng)建一個(gè)與Java線程對(duì)應(yīng)的本機(jī)線程
  6.  將與線程相關(guān)的描述符添加到JVM內(nèi)部數(shù)據(jù)結(jié)構(gòu)中
  7.  線程共享堆和方法區(qū)域

這段描述稍稍有點(diǎn)抽象,用數(shù)據(jù)來(lái)說(shuō)明創(chuàng)建一個(gè)線程(即便不干什么)需要多大空間呢?答案是大約 1M 左右

 

  1. java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -version 

上圖是我用 Java8 的測(cè)試結(jié)果,19個(gè)線程,預(yù)留和提交的大概都是19000+KB,平均每個(gè)線程大概需要 1M 左右的大?。↗ava11的結(jié)果完全不同,這個(gè)大家自行測(cè)試吧)

相信到這里你已經(jīng)明白了,對(duì)于性能要求嚴(yán)苛的現(xiàn)在,頻繁手動(dòng)創(chuàng)建/銷毀線程的代價(jià)是非常巨大的,解決方案自然也是你知道的線程池了

什么是線程池?

你常見的數(shù)據(jù)庫(kù)連接池,實(shí)例池,還有XX池,OO池,各種池,都是一種池化(pooling)思想,簡(jiǎn)而言之就是為了最大化收益,并最小化風(fēng)險(xiǎn),將資源統(tǒng)一在一起管理的思想

Java 也提供了它自己實(shí)現(xiàn)的線程池模型—— ThreadPoolExecutor。套用上面池化的想象來(lái)說(shuō),Java線程池就是為了最大化高并發(fā)帶來(lái)的性能提升,并最小化手動(dòng)創(chuàng)建線程的風(fēng)險(xiǎn),將多個(gè)線程統(tǒng)一在一起管理的思想

為了了解這個(gè)管理思想,我們當(dāng)前只需要關(guān)注 ThreadPoolExecutor 構(gòu)造方法就可以了 

  1. public ThreadPoolExecutor(int corePoolSize,  
  2.                               int maximumPoolSize,  
  3.                               long keepAliveTime,  
  4.                               TimeUnit unit,  
  5.                               BlockingQueue<Runnable> workQueue,  
  6.                               ThreadFactory threadFactory,  
  7.                               RejectedExecutionHandler handler) {  
  8.         if (corePoolSize < 0 ||  
  9.             maximumPoolSize <= 0 ||  
  10.             maximumPoolSize < corePoolSize ||  
  11.             keepAliveTime < 0 
  12.             throw new IllegalArgumentException();  
  13.         if (workQueue == null || threadFactory == null || handler == null)  
  14.             throw new NullPointerException();  
  15.         this.acc = System.getSecurityManager() == null ?  
  16.                 null :  
  17.                 AccessController.getContext();  
  18.         this.corePoolSize = corePoolSize;  
  19.         this.maximumPoolSize = maximumPoolSize;  
  20.         this.workQueue = workQueue;  
  21.         this.keepAliveTime = unit.toNanos(keepAliveTime);  
  22.         this.threadFactory = threadFactory;  
  23.         this.handler = handler;  

這么復(fù)雜的構(gòu)造方法在JDK中還真是不多見,為了個(gè)更形象化的讓大家理解這幾個(gè)核心參數(shù),我們以多數(shù)人都經(jīng)歷過的春運(yùn)(北京——上海)來(lái)說(shuō)明

序號(hào) 參數(shù)名稱 參數(shù)解釋 春運(yùn)形象說(shuō)明
1 corePoolSize 表示常駐核心線程數(shù),如果大于0,即使本地任務(wù)執(zhí)行完也不會(huì)被銷毀 日常固定的列車數(shù)輛(不管是不是春運(yùn),都要有固定這些車次運(yùn)行)
2 maximumPoolSize 表示線程池能夠容納可同時(shí)執(zhí)行的最大線程數(shù) 春運(yùn)客流量大,臨時(shí)加車,加車后,總列車次數(shù)不能超過這個(gè)最大值,否則就會(huì)出現(xiàn)調(diào)度不開等問題 (結(jié)合workqueue)
3 keepAliveTime 表示線程池中線程空閑的時(shí)間,當(dāng)空閑時(shí)間達(dá)到該值時(shí),線程會(huì)被銷毀,只剩下 corePoolSize 個(gè)線程位置 春運(yùn)壓力過后,臨時(shí)的加車(如果空閑時(shí)間超過keepAliveTime)就會(huì)被撤掉,只保留日常固定的列車車次數(shù)量用于日常運(yùn)營(yíng)
4 unit keepAliveTime 的時(shí)間單位,最終都會(huì)轉(zhuǎn)換成【納秒】,因?yàn)镃PU的執(zhí)行速度杠杠滴 keepAliveTime 的單位,春運(yùn)以【天】為計(jì)算單位
5 workQueue 當(dāng)請(qǐng)求的線程數(shù)大于 corePoolSize 時(shí),線程進(jìn)入該阻塞隊(duì)列 春運(yùn)壓力異常大,(達(dá)到corePoolSize)也不能滿足要求,所有乘坐請(qǐng)求都會(huì)進(jìn)入該阻塞隊(duì)列中排隊(duì), 隊(duì)列滿,還有額外請(qǐng)求,就需要加車了
6 threadFactory 顧名思義,線程工廠,用來(lái)生產(chǎn)一組相同任務(wù)的線程,同時(shí)也可以通過它增加前綴名,虛擬機(jī)棧分析時(shí)更清晰 比如(北京——上海)就屬于該段列車所有前綴,表明列車運(yùn)輸職責(zé)
7 handler 執(zhí)行拒絕策略,當(dāng) workQueue 達(dá)到上限,同時(shí)也達(dá)到 maximumPoolSize 就要通過這個(gè)來(lái)處理,比如拒絕,丟棄等,這是一種限流的保護(hù)措施 當(dāng)workQueue排隊(duì)也達(dá)到隊(duì)列最大上線,maximumPoolSize 就要提示無(wú)票等拒絕策略了,因?yàn)槲覀儾荒芗榆嚵耍?dāng)前所有車次已經(jīng)滿負(fù)載

整體來(lái)看就是這樣:

試想,如果有請(qǐng)求就新建一趟列車,請(qǐng)求結(jié)束就“銷毀”這趟列車,頻繁往復(fù)這樣操作,這樣的代價(jià)肯定是不能接受的。

可以看到,使用線程池不但能完成手動(dòng)創(chuàng)建線程可以做到的工作,同時(shí)也填補(bǔ)了手動(dòng)線程不能做到的空白。歸納起來(lái)說(shuō),線程池的作用包括:

  1.  利用線程池管理并服用線程,控制最大并發(fā)數(shù)(手動(dòng)創(chuàng)建線程很難得到保證)
  2.  實(shí)現(xiàn)任務(wù)線程隊(duì)列緩存策略和拒絕機(jī)制
  3.  實(shí)現(xiàn)某些與實(shí)踐相關(guān)的功能,如定時(shí)執(zhí)行,周期執(zhí)行等(比如列車指定時(shí)間運(yùn)行)
  4.  隔離線程環(huán)境,比如,交易服務(wù)和搜索服務(wù)在同一臺(tái)服務(wù)器上,分別開啟兩個(gè)線程池,交易線程的資源消耗明顯要大。因此,通過配置獨(dú)立的線程池,將較慢的交易服務(wù)與搜索服務(wù)個(gè)離開,避免個(gè)服務(wù)線程互相影響

相信到這里,你已經(jīng)了解線程池的基本思想了,在使用過程中還是有幾個(gè)注意事項(xiàng)要說(shuō)明一下的

線程池使用思想/注意事項(xiàng)

不能忽略的線程池拒絕策略

我們很難準(zhǔn)確的預(yù)測(cè)未來(lái)的最大并發(fā)量,所以定制合理的拒絕策略是必不可少的步驟。默認(rèn)情況, ThreadPoolExecutor 提供了四種拒絕策略:

  1.  AbortPolicy:默認(rèn)的拒絕策略,會(huì) throw RejectedExecutionException 拒絕
  2.  CallerRunsPolicy:提交任務(wù)的線程自己去執(zhí)行該任務(wù)
  3.  DiscardOldestPolicy:丟棄最老的任務(wù),其實(shí)就是把最早進(jìn)入工作隊(duì)列的任務(wù)丟棄,然后把新任務(wù)加入到工作隊(duì)列
  4.  DiscardPolicy:相當(dāng)大膽的策略,直接丟棄任務(wù),沒有任何異常拋出

不同的框架(Netty,Dubbo)都有不同的拒絕策略,我們也可以通過實(shí)現(xiàn) RejectedExecutionHandler 自定義的拒絕策略

對(duì)于采用何種策略,具體要看執(zhí)行的任務(wù)重要程度。如果是一些不重要任務(wù),可以選擇直接丟棄;如果是重要任務(wù),可以采用降級(jí)(所謂降級(jí)就是在服務(wù)無(wú)法正常提供功能的情況下,采取的補(bǔ)救措施。具體采用何種降級(jí)手段,這也是要看具體場(chǎng)景)處理,例如將任務(wù)信息插入數(shù)據(jù)庫(kù)或者消息隊(duì)列,啟用一個(gè)專門用作補(bǔ)償?shù)木€程池去進(jìn)行補(bǔ)償

沒有絕對(duì)的拒絕策略,只有適合那一個(gè),但在設(shè)計(jì)過程中千萬(wàn)不要忽略掉拒絕策略就可以

禁止使用Executors創(chuàng)建線程池

相信很多人都看到過這個(gè)問題(阿里巴巴Java開發(fā)手冊(cè)說(shuō)明禁止使用 Executors 創(chuàng)建線程池),我把出處(P247)截圖在此:

Executors 大大的簡(jiǎn)化了我們創(chuàng)建各種類型線程池的方式,為什么還不讓使用呢?

其實(shí),只要你打開看看它的靜態(tài)方法參數(shù)就會(huì)明白了 

  1. public static ExecutorService newFixedThreadPool(int nThreads) {  
  2.         return new ThreadPoolExecutor(nThreads, nThreads,  
  3.                                       0L, TimeUnit.MILLISECONDS,  
  4.                                       new LinkedBlockingQueue<Runnable>());  
  5.     } 

傳入的workQueue 是一個(gè)邊界為 Integer.MAX_VALUE 隊(duì)列,我們也可以變相的稱之為無(wú)界隊(duì)列了,因?yàn)檫吔缣罅?,這么大的等待隊(duì)列也是非常消耗內(nèi)存的 

  1. /**  
  2.  * Creates a {@code LinkedBlockingQueue} with a capacity of  
  3.  * {@link Integer#MAX_VALUE}.  
  4.  */  
  5. public LinkedBlockingQueue() {  
  6.   this(Integer.MAX_VALUE);  

另外該 ThreadPoolExecutor方法使用的是默認(rèn)拒絕策略(直接拒絕),但并不是所有業(yè)務(wù)場(chǎng)景都適合使用這個(gè)策略,當(dāng)很重要的請(qǐng)求過來(lái)直接選擇拒絕顯然是不合適的 

  1. public ThreadPoolExecutor(int corePoolSize,  
  2.                               int maximumPoolSize,  
  3.                               long keepAliveTime,  
  4.                               TimeUnit unit,  
  5.                               BlockingQueue<Runnable> workQueue) {  
  6.         this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
  7.              Executors.defaultThreadFactory(), defaultHandler);  
  8.     } 

總的來(lái)說(shuō),使用 Executors 創(chuàng)建的線程池太過于理想化,并不能滿足很多現(xiàn)實(shí)中的業(yè)務(wù)場(chǎng)景,所以要求我們通過 ThreadPoolExecutor來(lái)創(chuàng)建,并傳入合適的參數(shù)

總結(jié)

當(dāng)我們需要頻繁的創(chuàng)建線程時(shí),我們要考慮到通過線程池統(tǒng)一管理線程資源,避免不可控風(fēng)險(xiǎn)以及額外的開銷

了解了線程池的幾個(gè)核心參數(shù)概念后,我們也需要經(jīng)過調(diào)優(yōu)的過程來(lái)設(shè)置最佳線程參數(shù)值(這個(gè)過程時(shí)必不可少的)

線程池雖然彌補(bǔ)了手動(dòng)創(chuàng)建線程的缺陷和空白,同時(shí),合理的降級(jí)策略能大大增加系統(tǒng)的穩(wěn)定性

阿里巴巴手冊(cè)都是前輩們無(wú)數(shù)填坑后總結(jié)的精華,你也應(yīng)該遵守相應(yīng)的指示,結(jié)合自己的實(shí)際業(yè)務(wù)場(chǎng)景,設(shè)定合適的參數(shù)來(lái)創(chuàng)建線程池 

 

責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2019-11-13 14:38:34

Executors阿里線程池

2024-02-28 07:37:53

JavaExecutors工具

2024-01-03 08:15:35

Executors線程池線程

2025-04-17 08:47:23

2022-03-21 07:40:08

線程池Executors方式

2022-03-02 07:36:37

池化技術(shù)Java線程池

2024-07-15 08:20:24

2024-05-06 08:17:50

線程池機(jī)制線程數(shù)

2020-05-22 08:11:48

線程池JVM面試

2023-09-29 08:53:30

線程池java函數(shù)

2023-09-27 23:03:01

Java虛擬線程

2025-05-06 09:32:13

2019-10-30 21:27:51

Java中央處理器電腦

2012-02-29 13:26:20

Java

2024-01-26 06:30:46

C#線程本地存儲(chǔ)

2023-08-17 14:12:17

2018-03-13 09:34:36

Kubernetes容器系統(tǒng)

2021-09-11 15:26:23

Java多線程線程池

2022-03-07 07:33:16

線程池Java語(yǔ)言

2021-04-18 07:12:08

Dubbo線程池
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 狠狠爱网址 | 九九精品在线 | 91亚洲国产成人精品一区二三 | 国产第一页在线观看 | 国产精品亚洲一区二区三区在线 | 色花av| 欧美极品在线播放 | 91精品国产乱码麻豆白嫩 | 国产精品久久av | 国产精品区二区三区日本 | 久久国内 | 亚洲欧美一区二区三区国产精品 | 日韩av网址在线观看 | 亚洲一区二区视频 | 1级毛片| av网站在线免费观看 | 本道综合精品 | 亚洲国产精久久久久久久 | 中文字幕亚洲欧美 | 色必久久 | 亚洲精品日韩精品 | 91精品国产91久久久久久吃药 | 久久99蜜桃综合影院免费观看 | 高清人人天天夜夜曰狠狠狠狠 | 亚洲国产网 | 91福利网址 | 日本a级大片 | 九九在线精品视频 | 日本一二区视频 | 午夜精品一区二区三区在线播放 | 国产精品自拍视频网站 | 免费1区2区3区 | 日本精品视频 | 日韩二区| 欧产日产国产精品视频 | 久久av一区二区三区 | 精品国产一区二区三区免费 | 亚洲视频在线免费观看 | 久在线 | 成人午夜性成交 | 中文字幕高清 |