您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“怎么創(chuàng)建一個(gè)java線程”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
手動(dòng)創(chuàng)建線程有什么缺點(diǎn)?
不受控風(fēng)險(xiǎn)
頻繁創(chuàng)建開(kāi)銷(xiāo)大
不受控風(fēng)險(xiǎn)
這個(gè)缺點(diǎn),相信你也可以說(shuō)出一二
系統(tǒng)資源有限,每個(gè)人針對(duì)不同業(yè)務(wù)都可以手動(dòng)創(chuàng)建線程,并且創(chuàng)建標(biāo)準(zhǔn)不一樣(比如線程沒(méi)有名字)。當(dāng)系統(tǒng)運(yùn)行起來(lái),所有線程都在瘋狂搶占資源,無(wú)組織無(wú)紀(jì)律,混亂場(chǎng)面可想而知(出現(xiàn)問(wèn)題,自然也就不可能輕易的發(fā)現(xiàn)和解決)
如果有位神奇的小伙伴,為每個(gè)請(qǐng)求都創(chuàng)建一個(gè)線程,當(dāng)大量請(qǐng)求鋪面而來(lái)的時(shí)候,這好比一個(gè)正規(guī)木馬程序,內(nèi)存被無(wú)情榨干耗盡(你無(wú)情,你冷酷,你無(wú)理取鬧)
另外,過(guò)多的線程自然也會(huì)引起上下文切換的開(kāi)銷(xiāo)
總的來(lái)說(shuō),不受控風(fēng)險(xiǎn)很大
頻繁創(chuàng)建開(kāi)銷(xiāo)大
面試問(wèn): 頻繁手動(dòng)創(chuàng)建線程有什么問(wèn)題?
答: 開(kāi)銷(xiāo)大
這貌似是一個(gè)不假思索就可以回答出來(lái)的正確答案。那我要繼續(xù)問(wèn)了
面試官: 創(chuàng)建一個(gè)線程干了什么就開(kāi)銷(xiāo)大了?和我們創(chuàng)建一個(gè)普通 Java 對(duì)象有什么差別?
答: ... 嗯...啊
按照常規(guī)理解 new Thread() 創(chuàng)建一個(gè)線程和 new Object() 沒(méi)有什么差別。Java中萬(wàn)物接對(duì)象,因?yàn)?Thread 的老祖宗也是 Object
如果你真是這么理解的,說(shuō)明你對(duì)線程的生命周期還不是很理解,請(qǐng)回看之前的 Java線程生命周期這樣理解挺簡(jiǎn)單的
在這篇文章中我們明確說(shuō)明,new Thread() 在操作系統(tǒng)層面并沒(méi)有創(chuàng)建新的線程,這是編程語(yǔ)言特有的。真正轉(zhuǎn)換為操作系統(tǒng)層面創(chuàng)建一個(gè)線程,還要調(diào)用操作系統(tǒng)內(nèi)核的API,然后操作系統(tǒng)要為該線程分配一系列的資源
廢話不多說(shuō),我們將二者做個(gè)對(duì)比:
new Object() 過(guò)程
Object obj = new Object();
當(dāng)我需要【對(duì)象】時(shí),我就會(huì)給自己 new 一個(gè)(不知你是否和我一樣),這個(gè)過(guò)程你應(yīng)該很熟悉了:
分配一塊內(nèi)存 M
在內(nèi)存 M 上初始化該對(duì)象
將內(nèi)存 M 的地址賦值給引用變量 obj
就是這么簡(jiǎn)單
創(chuàng)建一個(gè)線程的過(guò)程
上面已經(jīng)提到了,創(chuàng)建一個(gè)線程還要調(diào)用操作系統(tǒng)內(nèi)核API。為了更好的理解創(chuàng)建并啟動(dòng)一個(gè)線程的開(kāi)銷(xiāo),我們需要看看 JVM 在背后幫我們做了哪些事情:
它為一個(gè)線程棧分配內(nèi)存,該棧為每個(gè)線程方法調(diào)用保存一個(gè)棧幀
每一棧幀由一個(gè)局部變量數(shù)組、返回值、操作數(shù)堆棧和常量池組成
一些支持本機(jī)方法的 jvm 也會(huì)分配一個(gè)本機(jī)堆棧
每個(gè)線程獲得一個(gè)程序計(jì)數(shù)器,告訴它當(dāng)前處理器執(zhí)行的指令是什么
系統(tǒng)創(chuàng)建一個(gè)與Java線程對(duì)應(yīng)的本機(jī)線程
將與線程相關(guān)的描述符添加到JVM內(nèi)部數(shù)據(jù)結(jié)構(gòu)中
線程共享堆和方法區(qū)域
這段描述稍稍有點(diǎn)抽象,用數(shù)據(jù)來(lái)說(shuō)明創(chuàng)建一個(gè)線程(即便不干什么)需要多大空間呢?答案是大約 1M 左右
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)建/銷(xiāo)毀線程的代價(jià)是非常巨大的,解決方案自然也是你知道的線程池了
什么是線程池?
你常見(jiàn)的數(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)造方法就可以了
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
這么復(fù)雜的構(gòu)造方法在JDK中還真是不多見(jiàn),為了個(gè)更形象化的讓大家理解這幾個(gè)核心參數(shù),我們以多數(shù)人都經(jīng)歷過(guò)的春運(yùn)(北京——上海)來(lái)說(shuō)明
序號(hào) | 參數(shù)名稱(chēng) | 參數(shù)解釋 | 春運(yùn)形象說(shuō)明 |
---|---|---|---|
1 | corePoolSize | 表示常駐核心線程數(shù),如果大于0,即使本地任務(wù)執(zhí)行完也不會(huì)被銷(xiāo)毀 | 日常固定的列車(chē)數(shù)輛(不管是不是春運(yùn),都要有固定這些車(chē)次運(yùn)行) |
2 | maximumPoolSize | 表示線程池能夠容納可同時(shí)執(zhí)行的最大線程數(shù) | 春運(yùn)客流量大,臨時(shí)加車(chē),加車(chē)后,總列車(chē)次數(shù)不能超過(guò)這個(gè)最大值,否則就會(huì)出現(xiàn)調(diào)度不開(kāi)等問(wèn)題 (結(jié)合workqueue) |
3 | keepAliveTime | 表示線程池中線程空閑的時(shí)間,當(dāng)空閑時(shí)間達(dá)到該值時(shí),線程會(huì)被銷(xiāo)毀,只剩下 corePoolSize 個(gè)線程位置 | 春運(yùn)壓力過(guò)后,臨時(shí)的加車(chē)(如果空閑時(shí)間超過(guò)keepAliveTime )就會(huì)被撤掉,只保留日常固定的列車(chē)車(chē)次數(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)求,就需要加車(chē)了 |
6 | threadFactory | 顧名思義,線程工廠,用來(lái)生產(chǎn)一組相同任務(wù)的線程,同時(shí)也可以通過(guò)它增加前綴名,虛擬機(jī)棧分析時(shí)更清晰 | 比如(北京——上海)就屬于該段列車(chē)所有前綴,表明列車(chē)運(yùn)輸職責(zé) |
7 | handler | 執(zhí)行拒絕策略,當(dāng) workQueue 達(dá)到上限,同時(shí)也達(dá)到 maximumPoolSize 就要通過(guò)這個(gè)來(lái)處理,比如拒絕,丟棄等,這是一種限流的保護(hù)措施 | 當(dāng)workQueue 排隊(duì)也達(dá)到隊(duì)列最大上線,maximumPoolSize 就要提示無(wú)票等拒絕策略了,因?yàn)槲覀儾荒芗榆?chē)了,當(dāng)前所有車(chē)次已經(jīng)滿負(fù)載 |
整體來(lái)看就是這樣:
試想,如果有請(qǐng)求就新建一趟列車(chē),請(qǐng)求結(jié)束就“銷(xiāo)毀”這趟列車(chē),頻繁往復(fù)這樣操作,這樣的代價(jià)肯定是不能接受的。
可以看到,使用線程池不但能完成手動(dòng)創(chuàng)建線程可以做到的工作,同時(shí)也填補(bǔ)了手動(dòng)線程不能做到的空白。歸納起來(lái)說(shuō),線程池的作用包括:
利用線程池管理并服用線程,控制最大并發(fā)數(shù)(手動(dòng)創(chuàng)建線程很難得到保證)
實(shí)現(xiàn)任務(wù)線程隊(duì)列緩存策略和拒絕機(jī)制
實(shí)現(xiàn)某些與實(shí)踐相關(guān)的功能,如定時(shí)執(zhí)行,周期執(zhí)行等(比如列車(chē)指定時(shí)間運(yùn)行)
隔離線程環(huán)境,比如,交易服務(wù)和搜索服務(wù)在同一臺(tái)服務(wù)器上,分別開(kāi)啟兩個(gè)線程池,交易線程的資源消耗明顯要大。因此,通過(guò)配置獨(dú)立的線程池,將較慢的交易服務(wù)與搜索服務(wù)個(gè)離開(kāi),避免個(gè)服務(wù)線程互相影響
相信到這里,你已經(jīng)了解線程池的基本思想了,在使用過(guò)程中還是有幾個(gè)注意事項(xiàng)要說(shuō)明一下的
線程池使用思想/注意事項(xiàng)
不能忽略的線程池拒絕策略
我們很難準(zhǔn)確的預(yù)測(cè)未來(lái)的最大并發(fā)量,所以定制合理的拒絕策略是必不可少的步驟。默認(rèn)情況, ThreadPoolExecutor 提供了四種拒絕策略:
AbortPolicy:默認(rèn)的拒絕策略,會(huì) throw RejectedExecutionException 拒絕
CallerRunsPolicy:提交任務(wù)的線程自己去執(zhí)行該任務(wù)
DiscardOldestPolicy:丟棄最老的任務(wù),其實(shí)就是把最早進(jìn)入工作隊(duì)列的任務(wù)丟棄,然后把新任務(wù)加入到工作隊(duì)列
DiscardPolicy:相當(dāng)大膽的策略,直接丟棄任務(wù),沒(méi)有任何異常拋出
不同的框架(Netty,Dubbo)都有不同的拒絕策略,我們也可以通過(guò)實(shí)現(xiàn) RejectedExecutionHandler 自定義的拒絕策略
對(duì)于采用何種策略,具體要看執(zhí)行的任務(wù)重要程度。如果是一些不重要任務(wù),可以選擇直接丟棄;如果是重要任務(wù),可以采用降級(jí)(所謂降級(jí)就是在服務(wù)無(wú)法正常提供功能的情況下,采取的補(bǔ)救措施。具體采用何種降級(jí)手段,這也是要看具體場(chǎng)景)處理,例如將任務(wù)信息插入數(shù)據(jù)庫(kù)或者消息隊(duì)列,啟用一個(gè)專(zhuān)門(mén)用作補(bǔ)償?shù)木€程池去進(jìn)行補(bǔ)償
沒(méi)有絕對(duì)的拒絕策略,只有適合那一個(gè),但在設(shè)計(jì)過(guò)程中千萬(wàn)不要忽略掉拒絕策略就可以
禁止使用Executors創(chuàng)建線程池
相信很多人都看到過(guò)這個(gè)問(wèn)題(阿里巴巴Java開(kāi)發(fā)手冊(cè)說(shuō)明禁止使用 Executors 創(chuàng)建線程池),我把出處(P247)截圖在此:
Executors 大大的簡(jiǎn)化了我們創(chuàng)建各種類(lèi)型線程池的方式,為什么還不讓使用呢?
其實(shí),只要你打開(kāi)看看它的靜態(tài)方法參數(shù)就會(huì)明白了
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
傳入的workQueue 是一個(gè)邊界為 Integer.MAX_VALUE 隊(duì)列,我們也可以變相的稱(chēng)之為無(wú)界隊(duì)列了,因?yàn)檫吔缣罅?,這么大的等待隊(duì)列也是非常消耗內(nèi)存的
/** * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}. */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
另外該 ThreadPoolExecutor方法使用的是默認(rèn)拒絕策略(直接拒絕),但并不是所有業(yè)務(wù)場(chǎng)景都適合使用這個(gè)策略,當(dāng)很重要的請(qǐng)求過(guò)來(lái)直接選擇拒絕顯然是不合適的
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
總的來(lái)說(shuō),使用 Executors 創(chuàng)建的線程池太過(guò)于理想化,并不能滿足很多現(xiàn)實(shí)中的業(yè)務(wù)場(chǎng)景,所以要求我們通過(guò) ThreadPoolExecutor來(lái)創(chuàng)建,并傳入合適的參數(shù)
“怎么創(chuàng)建一個(gè)java線程”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。