阿里Java編程規(guī)約【七】 并發(fā)處理

1. 【強(qiáng)制】獲取單例對(duì)象需要保證線程安全或南,其中的方法也要保證線程安全。
說(shuō)明:資源驅(qū)動(dòng)類寥闪、工具類敬飒、單例工廠類都需要注意。

2. 【強(qiáng)制】創(chuàng)建線程或線程池時(shí)請(qǐng)指定有意義的線程名稱硫豆,方便出錯(cuò)時(shí)回溯龙巨。
正例:自定義線程工廠,并且根據(jù)外部特征進(jìn)行分組熊响,比如旨别,來(lái)自同一機(jī)房的調(diào)用,把機(jī)房編號(hào)賦值給
whatFeatureOfGroup:

public class UserThreadFactory implements ThreadFactory {

    private final String namePrefix;
    private final AtomicInteger nextId = new AtomicInteger(1);
    // 定義線程組名稱汗茄,在利用 jstack 來(lái)排查問(wèn)題時(shí)秸弛,非常有幫助
    UserThreadFactory(String whatFeatureOfGroup) {    
        namePrefix = "FromUserThreadFactory's" + whatFeatureOfGroup + "-Worker-";
    }
    
    @Override
    public Thread newThread(Runnable task) {    
        String name = namePrefix + nextId.getAndIncrement();    
        Thread thread = new Thread(null, task, name, 0, false);    
        System.out.println(thread.getName());    
        return thread;    
    }    
}

3. 【強(qiáng)制】線程資源必須通過(guò)線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程洪碳。
說(shuō)明:線程池的好處是減少在創(chuàng)建和銷毀線程上所消耗的時(shí)間以及系統(tǒng)資源的開(kāi)銷递览,解決資源不足的問(wèn)題。如果不使用
線程池瞳腌,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過(guò)度切換”的問(wèn)題绞铃。

我的筆記:使用線程池緩存線程可以提高效率,另外線程池幫我們做了管理線程的事情嫂侍,提供了優(yōu)雅關(guān)機(jī)儿捧、interrupt 等待 IO 的線程冷离,飽和策略等功能。

4. 【強(qiáng)制】線程池不允許使用 Executors 去創(chuàng)建纯命,而是通過(guò) ThreadPoolExecutor 的方式,這樣的處理方
式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則痹栖,規(guī)避資源耗盡的風(fēng)險(xiǎn)亿汞。
說(shuō)明:Executors 返回的線程池對(duì)象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求揪阿,從而導(dǎo)致 OOM疗我。
2)CachedThreadPool:
允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線程南捂,從而導(dǎo)致 OOM吴裤。
3)ScheduledThreadPool:
允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求溺健,從而導(dǎo)致 OOM麦牺。

5. 【強(qiáng)制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量鞭缭,如果定義為 static剖膳,必須
加鎖,或者使用 DateUtils 工具類岭辣。
正例:注意線程安全吱晒,使用 DateUtils。亦推薦如下處理:

private static final ThreadLocal<DateFormat> dateStyle = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd");
    }
};

說(shuō)明:如果是 JDK8 的應(yīng)用沦童,可以使用 Instant 代替 Date仑濒,LocalDateTime 代替 Calendar,DateTimeFormatter 代替
SimpleDateFormat偷遗,官方給出的解釋:simple beautiful strong immutable thread-safe墩瞳。

6. 【強(qiáng)制】必須回收自定義的 ThreadLocal 變量記錄的當(dāng)前線程的值,尤其在線程池場(chǎng)景下鹦肿,線程經(jīng)常會(huì)
被復(fù)用矗烛,如果不清理自定義的 ThreadLocal 變量,可能會(huì)影響后續(xù)業(yè)務(wù)邏輯和造成內(nèi)存泄露等問(wèn)題箩溃。
盡量在代碼中使用 try-finally 塊進(jìn)行回收瞭吃。
正例:

objectThreadLocal.set(userInfo);
try {
    // ...
} finally {
    objectThreadLocal.remove();
}

7. 【強(qiáng)制】高并發(fā)時(shí),同步調(diào)用應(yīng)該去考量鎖的性能損耗涣旨。能用無(wú)鎖數(shù)據(jù)結(jié)構(gòu)歪架,就不要用鎖;能鎖區(qū)塊霹陡,就
不要鎖整個(gè)方法體和蚪;能用對(duì)象鎖止状,就不要用類鎖。
說(shuō)明:盡可能使加鎖的代碼塊工作量盡可能的小攒霹,避免在鎖代碼塊中調(diào)用 RPC 方法怯疤。

筆記:優(yōu)先無(wú)鎖,不用鎖能解決的一定不要用鎖催束,即使用鎖也要控制粒度集峦,越細(xì)越好。

8. 【強(qiáng)制】對(duì)多個(gè)資源抠刺、數(shù)據(jù)庫(kù)表塔淤、對(duì)象同時(shí)加鎖時(shí),需要保持一致的加鎖順序速妖,否則可能會(huì)造成死鎖高蜂。
說(shuō)明:線程一需要對(duì)表 A、B罕容、C 依次全部加鎖后才可以進(jìn)行更新操作备恤,那么線程二的加鎖順序也必須是 A、B锦秒、C烘跺,否則可
能出現(xiàn)死鎖。

筆記:解決死鎖的方法:按順序鎖資源脂崔、超時(shí)滤淳、優(yōu)先級(jí)、死鎖檢測(cè)等砌左〔备溃可參考哲學(xué)家進(jìn)餐問(wèn)題學(xué)習(xí)更深入的并發(fā)機(jī)制。

9. 【強(qiáng)制】在使用阻塞等待獲取鎖的方式中汇歹,必須在 try 代碼塊之外屁擅,并且在加鎖方法與 try 代碼塊之間沒(méi)
有任何可能拋出異常的方法調(diào)用,避免加鎖成功后产弹,在 finally 中無(wú)法解鎖派歌。
說(shuō)明一:在 lock 方法與 try 代碼塊之間的方法調(diào)用拋出異常,無(wú)法解鎖痰哨,造成其它線程無(wú)法成功獲取鎖胶果。
說(shuō)明二:如果 lock 方法在 try 代碼塊之內(nèi),可能由于其它方法拋出異常斤斧,導(dǎo)致在 finally 代碼塊中早抠,unlock 對(duì)未加鎖的對(duì)
象解鎖,它會(huì)調(diào)用 AQS 的 tryRelease 方法(取決于具體實(shí)現(xiàn)類)撬讽,拋出 IllegalMonitorStateException 異常蕊连。
說(shuō)明三:在 Lock 對(duì)象的 lock 方法實(shí)現(xiàn)中可能拋出 unchecked 異常悬垃,產(chǎn)生的后果與說(shuō)明二相同。
正例:

Lock lock = new XxxLock();
// ...
lock.lock();
try {
    doSomething();
    doOthers();
} finally {
    lock.unlock();
}

反例:

Lock lock = new XxxLock();
// ...
try {
  // 如果此處拋出異常甘苍,則直接執(zhí)行 finally 代碼塊
  doSomething();
  // 無(wú)論加鎖是否成功尝蠕,finally 代碼塊都會(huì)執(zhí)行
  lock.lock();
  doOthers();
} finally {
  lock.unlock();
}

10\. 【強(qiáng)制】在使用嘗試機(jī)制來(lái)獲取鎖的方式中,進(jìn)入業(yè)務(wù)代碼塊之前载庭,必須先判斷當(dāng)前線程是否持有鎖趟佃。
鎖的釋放規(guī)則與鎖的阻塞等待方式相同。
說(shuō)明:Lock 對(duì)象的 unlock 方法在執(zhí)行時(shí)昧捷,它會(huì)調(diào)用 AQS 的 tryRelease 方法(取決于具體實(shí)現(xiàn)類),如果當(dāng)前線程不
持有鎖罐寨,則拋出 IllegalMonitorStateException 異常靡挥。
正例:

```java
Lock lock = new XxxLock();
// ...
boolean isLocked = lock.tryLock();
if (isLocked) {
    try {
        doSomething();
        doOthers();
    } finally {
        lock.unlock();
    }
}

11. 【強(qiáng)制】并發(fā)修改同一記錄時(shí),避免更新丟失鸯绿,需要加鎖跋破。要么在應(yīng)用層加鎖,要么在緩存加鎖瓶蝴,要么
在數(shù)據(jù)庫(kù)層使用樂(lè)觀鎖毒返,使用 version 作為更新依據(jù)。
說(shuō)明:如果每次訪問(wèn)沖突概率小于 20%舷手,推薦使用樂(lè)觀鎖拧簸,否則使用悲觀鎖。樂(lè)觀鎖的重試次數(shù)不得小于 3 次男窟。

12. 【強(qiáng)制】多線程并行處理定時(shí)任務(wù)時(shí)盆赤,Timer 運(yùn)行多個(gè) TimeTask 時(shí),只要其中之一沒(méi)有捕獲拋出的異
常歉眷,其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行牺六,使用 ScheduledExecutorService 則沒(méi)有這個(gè)問(wèn)題。

13.【推薦】資金相關(guān)的金融敏感信息汗捡,使用悲觀鎖策略淑际。
說(shuō)明:樂(lè)觀鎖在獲得鎖的同時(shí)已經(jīng)完成了更新操作,校驗(yàn)邏輯容易出現(xiàn)漏洞扇住,另外春缕,樂(lè)觀鎖對(duì)沖突的解決策略有較復(fù)雜
的要求,處理不當(dāng)容易造成系統(tǒng)壓力或數(shù)據(jù)異常艘蹋,所以資金相關(guān)的金融敏感信息不建議使用樂(lè)觀鎖更新淡溯。
正例:悲觀鎖遵循一鎖二判三更新四釋放的原則。

14.【推薦】使用 CountDownLatch 進(jìn)行異步轉(zhuǎn)同步操作簿训,每個(gè)線程退出前必須調(diào)用 countDown 方法咱娶,線
程執(zhí)行代碼注意 catch 異常米间,確保 countDown 方法被執(zhí)行到,避免主線程無(wú)法執(zhí)行至 await 方法膘侮,
直到超時(shí)才返回結(jié)果屈糊。
說(shuō)明:注意,子線程拋出異常堆棧琼了,不能在主線程 try-catch 到逻锐。

筆記:CountDownLatch 存在于 java.util.concurrent 包下。這個(gè)類能夠使一個(gè)線程等待其他線程完成各自的工作后再執(zhí)行雕薪。請(qǐng)?jiān)?try...finally 語(yǔ)句里執(zhí)行 countDown 方法昧诱,與關(guān)閉資源類似。

15.【推薦】避免 Random 實(shí)例被多線程使用所袁,雖然共享該實(shí)例是線程安全的盏档,但會(huì)因競(jìng)爭(zhēng)同一 seed 導(dǎo)致
的性能下降。
說(shuō)明:Random 實(shí)例包括 java.util.Random 的實(shí)例或者 Math.random() 的方式燥爷。
正例:在 JDK7 之后蜈亩,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前前翎,需要編碼保證每個(gè)線程持有一個(gè)
單獨(dú)的 Random 實(shí)例稚配。

16.【推薦】通過(guò)雙重檢查鎖(double-checked locking),實(shí)現(xiàn)延遲初始化需要將目標(biāo)屬性聲明為
volatile 型港华,(比如修改 helper 的屬性聲明為 private volatile Helper helper = null;)道川。
正例:

public class LazyInitDemo {
    private volatile Helper helper = null;
    public Helper getHelper() {
    if (helper == null) {    
        synchronized(this) {  
            if (helper == null) {   
                helper = new Helper();   
            }    
        }   
    }   
    return helper;   
}    
// other methods and fields...   
}

筆記:請(qǐng)參考參考The "Double-Checked Locking is Broken" Declaration

17.【參考】volatile 解決多線程內(nèi)存不可見(jiàn)問(wèn)題對(duì)于一寫多讀,是可以解決變量同步問(wèn)題立宜,但是如果多
寫愤惰,同樣無(wú)法解決線程安全問(wèn)題。
說(shuō)明:如果是 count++ 操作赘理,使用如下類實(shí)現(xiàn):

AtomicInteger count = new AtomicInteger();
count.addAndGet(1);

如果是 JDK8宦言,推薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂(lè)觀鎖的重試次數(shù))商模。

筆記:volatile只有內(nèi)存可見(jiàn)性語(yǔ)義奠旺,synchronized有互斥語(yǔ)義,一寫多讀使用volatile就可以施流,多寫就必須使用synchronized响疚,fetch-mod-get也必須使用synchronized。

18. 【參考】HashMap 在容量不夠進(jìn)行 resize 時(shí)由于高并發(fā)可能出現(xiàn)死鏈瞪醋,導(dǎo)致 CPU 飆升忿晕,在開(kāi)發(fā)過(guò)程
中注意規(guī)避此風(fēng)險(xiǎn)。

19. 【參考】ThreadLocal 對(duì)象使用 static 修飾银受,ThreadLocal 無(wú)法解決共享對(duì)象的更新問(wèn)題践盼。
說(shuō)明:這個(gè)變量是針對(duì)一個(gè)線程內(nèi)所有操作共享的鸦采,所以設(shè)置為靜態(tài)變量,所有此類實(shí)例共享此靜態(tài)變量咕幻,也就是說(shuō)在
類第一次被使用時(shí)裝載渔伯,只分配一塊存儲(chǔ)空間,所有此類的對(duì)象(只要是這個(gè)線程內(nèi)定義的)都可以操控這個(gè)變量肄程。

筆記:ThreadLocal 為解決多線程程序的并發(fā)問(wèn)題提供了一種新思路锣吼。當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本蓝厌,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本玄叠,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。ThreadLocal實(shí)際上是一個(gè)從線程ID到變量的Map拓提,每次取得ThreadLocal變量读恃,實(shí)際上是先取得當(dāng)前線程ID,再用當(dāng)前線程ID取得關(guān)聯(lián)的變量崎苗。ThreadLocal 使用了 WeakHashMap,在 key 被回收的時(shí)候舀寓,value 也被回收了胆数,不用擔(dān)心內(nèi)存泄露。

參考

  1. 2022 Java開(kāi)發(fā)手冊(cè)(黃山版).pdf
  2. 《編寫高質(zhì)量代碼:改善Java程序的151個(gè)建議》
  3. 白話阿里巴巴Java開(kāi)發(fā)手冊(cè)(安全規(guī)約) - 李艷鵬 - 簡(jiǎn)書(shū)(http://www.reibang.com/p/9528c4ea1504)
  4. Java并發(fā)編程的藝術(shù)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末互墓,一起剝皮案震驚了整個(gè)濱河市必尼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篡撵,老刑警劉巖判莉,帶你破解...
    沈念sama閱讀 211,423評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異育谬,居然都是意外死亡券盅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門膛檀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锰镀,“玉大人,你說(shuō)我怎么就攤上這事咖刃∮韭” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,019評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵嚎杨,是天一觀的道長(zhǎng)花鹅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)枫浙,這世上最難降的妖魔是什么刨肃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,443評(píng)論 1 283
  • 正文 為了忘掉前任古拴,我火速辦了婚禮,結(jié)果婚禮上之景,老公的妹妹穿的比我還像新娘斤富。我一直安慰自己,他們只是感情好锻狗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布满力。 她就那樣靜靜地躺著,像睡著了一般轻纪。 火紅的嫁衣襯著肌膚如雪油额。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,798評(píng)論 1 290
  • 那天刻帚,我揣著相機(jī)與錄音潦嘶,去河邊找鬼。 笑死崇众,一個(gè)胖子當(dāng)著我的面吹牛掂僵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播顷歌,決...
    沈念sama閱讀 38,941評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼锰蓬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了眯漩?” 一聲冷哼從身側(cè)響起芹扭,我...
    開(kāi)封第一講書(shū)人閱讀 37,704評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赦抖,沒(méi)想到半個(gè)月后舱卡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,152評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡队萤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評(píng)論 2 327
  • 正文 我和宋清朗相戀三年轮锥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片要尔。...
    茶點(diǎn)故事閱讀 38,629評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡交胚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盈电,到底是詐尸還是另有隱情蝴簇,我是刑警寧澤,帶...
    沈念sama閱讀 34,295評(píng)論 4 329
  • 正文 年R本政府宣布匆帚,位于F島的核電站熬词,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜互拾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評(píng)論 3 313
  • 文/蒙蒙 一歪今、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颜矿,春花似錦寄猩、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至箍铭,卻和暖如春泊柬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诈火。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,978評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工兽赁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冷守。 一個(gè)月前我還...
    沈念sama閱讀 46,333評(píng)論 2 360
  • 正文 我出身青樓刀崖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拍摇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子亮钦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評(píng)論 2 348