中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Java線程的5個(gè)使用技巧

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

  Java線程有哪些不太為人所知的技巧與用法?

  蘿卜白菜各有所愛(ài)。像我就喜歡Java。學(xué)無(wú)止境,這也是我喜歡它的一個(gè)原因。日常工作中你所用到的工具,通常都有些你從來(lái)沒(méi)有了解過(guò)的東西,比方說(shuō)某個(gè)方法或者是一些有趣的用法。比如說(shuō)線程。沒(méi)錯(cuò),就是線程。或者確切說(shuō)是Thread這個(gè)類。當(dāng)我們?cè)跇?gòu)建高可擴(kuò)展性系統(tǒng)的時(shí)候,通常會(huì)面臨各種各樣的并發(fā)編程的問(wèn)題,不過(guò)我們現(xiàn)在所要講的可能會(huì)略有不同。

  從本文中你將會(huì)看到線程提供的一些不太常用的方法及技術(shù)。不管你是初學(xué)者還是高級(jí)用戶或者是Java專家,希望都能看一下哪些是你已經(jīng)知道的,而哪些是剛了解的。如果你認(rèn)為關(guān)于線程還有什么值得分享給大家的,希望能在下面積極回復(fù)。那我們就先開(kāi)始吧。

  初學(xué)者

  1.線程名

  程序中的每個(gè)線程都有一個(gè)名字,創(chuàng)建線程的時(shí)候會(huì)給它分配一個(gè)簡(jiǎn)單的Java字符串來(lái)作為線程名。默認(rèn)的名字是”Thread-0″, “Thread-1″, “Thread-2″等等,F(xiàn)在有趣的事情來(lái)了——Thread提供了兩種方式來(lái)設(shè)置線程名:

  • 線程構(gòu)造函數(shù),下面是最簡(jiǎn)單的一個(gè)實(shí)現(xiàn):
class SuchThread extends Thread {

    Public void run() {
        System.out.println ("Hi Mom! " + getName());
    }

}

SuchThread wow = new SuchThread("much-name");
  • 線程名setter方法:
wow.setName(“Just another thread name”);

  沒(méi)錯(cuò),線程名是可變的。因此我們可以在運(yùn)行時(shí)修改它的名字,而不用在初始化的時(shí)候就指定好。name字段其實(shí)就是一個(gè)簡(jiǎn)單的字符串對(duì)象。也就是說(shuō)它能達(dá)到2³¹-1個(gè)字符那么長(zhǎng)(Integer.MAX_VALUE)。這足夠用了。注意這個(gè)名字并不是一個(gè)唯一性的標(biāo)識(shí),因此不同的線程也可以擁有同樣的線程名。還有一點(diǎn)就是,不要把null用作線程名,否則會(huì)拋出異常(當(dāng)然了,”null”還是可以的)。

  使用線程名來(lái)調(diào)試問(wèn)題

  既然可以設(shè)置線程名,那么如果遵循一定的命名規(guī)則的話,出了問(wèn)題的時(shí)候排查起來(lái)就能更容易一些。“Thread-6″這樣的名字看起來(lái)就太沒(méi)心沒(méi)肺了,肯定有比它更好的名字。在處理用戶請(qǐng)求的時(shí)候,可以將事務(wù)ID追加到線程名后面,這樣能顯著減少你排查問(wèn)題的時(shí)間。

“pool-1-thread-1″ #17 prio=5 os_prio=31 tid=0x00007f9d620c9800
nid=0x6d03 in Object.wait() [0x000000013ebcc000]

  “pool-1-thread-1″,這也太嚴(yán)肅了吧。我們來(lái)看下這是什么情況,給它起一個(gè)好點(diǎn)的名字:

Thread.currentThread().setName(Context + TID + Params + current Time, ...);

  現(xiàn)在我們?cè)賮?lái)運(yùn)行下jstack,情況便豁然開(kāi)朗了:

”Queue Processing Thread, MessageID: AB5CAD, type:
AnalyzeGraph, queue: ACTIVE_PROD, Transaction_ID: 5678956,
Start Time: 30/12/2014 17:37″ #17 prio=5 os_prio=31 tid=0x00007f9d620c9800
nid=0x6d03 in Object.wait() [0x000000013ebcc000]

  如果我們能知道線程在做什么,這樣當(dāng)它出問(wèn)題的時(shí)候,至少可以拿到事務(wù)ID來(lái)開(kāi)始排查。你可以回溯這個(gè)問(wèn)題,復(fù)現(xiàn)它,然后定位問(wèn)題并搞定它。如果你想知道jstack有什么給力的用法,可以看下這篇文章。

  2. 線程優(yōu)先級(jí)

  線程還有一個(gè)有意思的屬性就是它的優(yōu)先級(jí)。線程的優(yōu)先級(jí)介于1 (MINPRIORITY)到10 (MAXPRIORITY)之間,主線程默認(rèn)是5(NORM_PRIORITY)。每個(gè)新線程都默認(rèn)繼承父線程的優(yōu)先級(jí),因此如果你沒(méi)有設(shè)置過(guò)的話,所有線程的優(yōu)先級(jí)都是5。這個(gè)是通常被忽視的屬性,我們可以通過(guò)getPriority()與setPriority()方法來(lái)獲取及修改它的值。線程的構(gòu)造函數(shù)里是沒(méi)有這個(gè)功能的。

  什么地方會(huì)用到優(yōu)先級(jí)?

  當(dāng)然并不是所有的線程都是平等的,有的線程需要立即引起CPU的重視,而有些線程則只是后臺(tái)任務(wù)而已。優(yōu)先級(jí)就是用來(lái)把這些告訴給操作系統(tǒng)的線程調(diào)度器的。在Takipi中,這是我們開(kāi)發(fā)的一錯(cuò)誤跟蹤及排查的工具,負(fù)責(zé)處理用戶異常的線程的優(yōu)先級(jí)是MAX_PRIORITY,而那些只是在上報(bào)新的部署情況的線程,它們的優(yōu)先級(jí)就要低一些。你可能會(huì)覺(jué)得優(yōu)先級(jí)高的線程從JVM的線程調(diào)度器那得到的時(shí)間會(huì)多一些。但其實(shí)并都是這樣的。

  在操作系統(tǒng)層面,每一個(gè)新線程都會(huì)對(duì)應(yīng)一個(gè)本地線程,你所設(shè)置的Java線程的優(yōu)先級(jí)會(huì)被轉(zhuǎn)化成本地線程的優(yōu)先級(jí),這個(gè)在各個(gè)平臺(tái)上是不一樣的。在Linux上,你可以打開(kāi)“-XX:+UseThreadPriorities”選項(xiàng)來(lái)啟用這項(xiàng)功能。正如前面所說(shuō)的,線程優(yōu)先級(jí)只是你所提供的一個(gè)建議。和Linux本地的優(yōu)先級(jí)相比,Java線程的優(yōu)先級(jí)并不能覆蓋全所有的級(jí)別(Linux共有1到99個(gè)優(yōu)先級(jí),線程的優(yōu)先級(jí)在是-20到20之間)。最大的好處就是你所設(shè)定的優(yōu)先級(jí)能在每個(gè)線程獲得的CPU時(shí)間上有所體現(xiàn),不過(guò)完全依賴于線程優(yōu)先級(jí)的做法是不推薦的。

  進(jìn)階篇

  3.線程本地存儲(chǔ)

  這個(gè)和前面提到的兩個(gè)略有不同。ThreadLocal是在Thread類之外實(shí)現(xiàn)的一個(gè)功能(java.lang.ThreadLocal),但它會(huì)為每個(gè)線程分別存儲(chǔ)一份唯一的數(shù)據(jù)。正如它的名字所說(shuō)的,它為線程提供了本地存儲(chǔ),也就是說(shuō)你所創(chuàng)建出來(lái)變量對(duì)每個(gè)線程實(shí)例來(lái)說(shuō)都是唯一的。和線程名,線程優(yōu)先級(jí)類似,你可以自定義出一些屬性,就好像它們是存儲(chǔ)在Thread線程內(nèi)部一樣,是不是覺(jué)得酷?不過(guò)先別高興得太早了,有幾句丑話得先說(shuō)在前頭。

  創(chuàng)建ThreadLocal有兩種推薦方式:要么是靜態(tài)變量,要么是單例實(shí)例中的屬性,這樣可以是非靜態(tài)的。注意,它的作用域是全局的,只不過(guò)對(duì)訪問(wèn)它的線程而言好像是本地的而已。在下面這個(gè)例子中,ThreadLocal里面存儲(chǔ)了一個(gè)數(shù)據(jù)結(jié)構(gòu),這樣我們可以很容易地訪問(wèn)到它:

public static class CriticalData
{
    public int transactionId;
    public int username;
}

public static final ThreadLocal<CriticalData> globalData =
    new ThreadLocal<CriticalData>();

  一旦獲取到了ThreadLocal對(duì)象,就可以通過(guò) globalData.set()和globalData.get()方法來(lái)對(duì)它進(jìn)行操作了。

  全局變量?這不是什么好事

  也盡然。ThreadLocal可以用來(lái)存儲(chǔ)事務(wù)ID。如果代碼中出現(xiàn)未捕獲異常的時(shí)候它就相當(dāng)有用了。最佳實(shí)踐是設(shè)置一個(gè)UncaughtExceptionHandler,這個(gè)是Thread類本身就支持的,但是你得自己去實(shí)現(xiàn)一下這個(gè)接口。一旦執(zhí)行到了UncaughtExceptionHandler里,就幾乎沒(méi)有任何線索能夠知道到底發(fā)生了什么事情了。這會(huì)兒你能獲取到的就只有Thread對(duì)象,之前導(dǎo)致異常發(fā)生的所有變量都無(wú)法再訪問(wèn)了,因?yàn)槟切家呀?jīng)被彈出了。一旦到了UncaughtExceptionHandler里,這個(gè)線程就只剩下最后一口氣了,唯一能抓住的最后一根稻草就是ThreadLocal。

  我們來(lái)試下這么做:

System.err.println("Transaction ID " + globalData.get().transactionId);

  我們可以將一些與錯(cuò)誤相關(guān)的有價(jià)值的上下文信息給存儲(chǔ)到里面添。ThreadLocal還有一個(gè)更有創(chuàng)意的用法,就是用它來(lái)分配一塊特定的內(nèi)存,這樣工作線程可以把它當(dāng)作緩存來(lái)不停地使用。當(dāng)然了,這有沒(méi)有用得看你在CPU和內(nèi)存之間是怎么權(quán)衡的了。沒(méi)錯(cuò),ThreadLocal需要注意的就是會(huì)造成內(nèi)存空間的浪費(fèi)。只要線程還活著,那么它就會(huì)一直存在,除非你主動(dòng)釋放否則它是不會(huì)被回收的。因此如果使用它的話你最好注意一下,盡量保持簡(jiǎn)單。

  4. 用戶線程及守護(hù)線程

  我們?cè)倩氐絋hread類。程序中的每個(gè)線程都會(huì)有一個(gè)狀態(tài),要么是用戶狀態(tài),要么是守護(hù)狀態(tài)。換句話說(shuō),要么是前臺(tái)線程要么是后臺(tái)線程。主線程默認(rèn)是用戶線程,每個(gè)新線程都會(huì)從創(chuàng)建它的線程中繼承線程狀態(tài)。因此如果你把一個(gè)線程設(shè)置成守護(hù)線程,那么它所創(chuàng)建的所有線程都會(huì)被標(biāo)記成守護(hù)線程。如果程序中的所有線程都是守護(hù)線程的話,那么這個(gè)進(jìn)程便會(huì)終止。我們可以通過(guò)Boolean .setDaemon(true)和.isDaemon()方法來(lái)查看及設(shè)置線程狀態(tài)。

  什么時(shí)候會(huì)用到守護(hù)線程?

  如果進(jìn)程不必等到某個(gè)線程結(jié)束才能終止,那么這個(gè)線程就可以設(shè)置成守護(hù)線程。這省掉了正常關(guān)閉線程的那些麻煩事,可以立即將線程結(jié)束掉。換個(gè)角度來(lái)說(shuō),如果一個(gè)正在執(zhí)行某個(gè)操作的線程必須要正確地關(guān)閉掉否則就會(huì)出現(xiàn)不好的后果的話,那么這個(gè)線程就應(yīng)該是用戶線程。通常都是些關(guān)鍵的事務(wù),比方說(shuō),數(shù)據(jù)庫(kù)錄入或者更新,這些操作都是不能中斷的。

  專家級(jí)

  5. 處理器親和性(Processor Affinity)

  這里要講的會(huì)更靠近硬件,也就是說(shuō),當(dāng)軟件遇上了硬件。處理器親和性使得你能夠?qū)⒕程或者進(jìn)程綁定到特定的CPU核上。這意味著只要是某個(gè)特定的線程,它就肯定只會(huì)在某個(gè)特定的CPU核上執(zhí)行。通常來(lái)講如何綁定是由操作系統(tǒng)的線程調(diào)度器根據(jù)它自己的邏輯來(lái)決定的,它很可能會(huì)將我們前面提到的線程優(yōu)先級(jí)也一并考慮進(jìn)來(lái)。

  這么做的好處在于CPU緩存。如果某個(gè)線程只會(huì)在某個(gè)核上運(yùn)行,那么它的數(shù)據(jù)恰好在緩存里的概率就大大提高了。如果數(shù)據(jù)正好就在CPU緩存里,那么就沒(méi)有必要重新再?gòu)膬?nèi)存里加載了。你所節(jié)省的這幾毫秒時(shí)間就能用在刀刃上,在這段時(shí)間里代碼可以馬上開(kāi)始執(zhí)行,也就能更好地利用所分配給它的CPU時(shí)間。當(dāng)然了,操作系統(tǒng)層面可能會(huì)存在某種優(yōu)化,硬件架構(gòu)當(dāng)然也是個(gè)很重要的因素,但利用了處理器的親和性至少能夠減小線程切換CPU的機(jī)率。

  由于這里摻雜著多種因素,處理器親和性到底對(duì)吞吐量有多大的影響,最好還是通過(guò)測(cè)試的方式來(lái)進(jìn)行證明。也許這個(gè)方法并不是總能顯著地提升性能,但至少有一個(gè)好處就是吞吐量會(huì)相對(duì)穩(wěn)定。親和策略可以細(xì)化到非常細(xì)的粒度上,這取決于你具體想要什么。高頻交易行業(yè)便是這一策略最能大顯身手的場(chǎng)景之一。

  處理器親和性的測(cè)試

  Java對(duì)處理器的親和性并沒(méi)有原生的支持,當(dāng)然了,故事也還沒(méi)有就此結(jié)束。在Linux上,我們可以通過(guò)taskset命令來(lái)設(shè)置進(jìn)程的親和性。假設(shè)我們現(xiàn)在有一個(gè)Java進(jìn)程在運(yùn)行,而我們希望將它綁定到某個(gè)特定的CPU上:

taskset -c 1 “java AboutToBePinned”

  如果是一個(gè)已經(jīng)在運(yùn)行了的進(jìn)程:

taskset -c 1 <PID>

  要想深入到線程級(jí)別還得再加些代碼才行。所幸的是,有一個(gè)開(kāi)源庫(kù)能完成這樣的功能:Java-Thread-Affinity。這個(gè)庫(kù)是由OpenHFT的Peter Lawrey開(kāi)發(fā)的,實(shí)現(xiàn)這一功能最簡(jiǎn)單直接的方式應(yīng)該就是使用這個(gè)庫(kù)了。我們通過(guò)一個(gè)例子來(lái)快速看下如何綁定某個(gè)線程,關(guān)于該庫(kù)的更多細(xì)節(jié)請(qǐng)參考它在Github上的文檔:

AffinityLock al = AffinityLock.acquireLock();

  這樣就可以了。關(guān)于獲取鎖的一些更高級(jí)的選項(xiàng)——比如說(shuō)根據(jù)不同的策略來(lái)選擇CPU——在Github上都有詳細(xì)的說(shuō)明。

  結(jié)論

  本文我們介紹了關(guān)于線程的5點(diǎn)知識(shí):線程名,線程本地存儲(chǔ),優(yōu)先級(jí),守護(hù)線程以及處理器親和性。希望這能為你日常工作中所用到的內(nèi)容打開(kāi)一扇新的窗戶,期待你們的反饋!還有什么有關(guān)線程處理的方法可以分享給大家的嗎,請(qǐng)不吝賜教。

標(biāo)簽: linux 代碼 數(shù)據(jù)庫(kù)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:引用 exit、return、_exit、_Exit這幾個(gè)函數(shù)的區(qū)別

下一篇:Android反編譯:smali語(yǔ)法