\1. 【強制】獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。
\2. 【強制】創(chuàng)建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
public class UserThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger nextId = new AtomicInteger(1);
// 定義線程組名稱,在利用 jstack 來排查問題時,非常有幫助
UserThreadFactory(String whatFeatureOfGroup) {
namePrefix = "From UserThreadFactory'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. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創(chuàng)建線程。
\4. 【強制】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險。
\5. 【強制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為 static,必須加鎖,或者使用 DateUtils 工具類。
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
\6. 【強制】必須回收自定義的 ThreadLocal 變量,尤其在線程池場景下,線程經(jīng)常會被復用,如果不清理自定義的 ThreadLocal 變量,可能會影響后續(xù)業(yè)務邏輯和造成內(nèi)存泄露等問題。盡量在代理中使用 try-finally 塊進行回收。
objectThreadLocal.set(userInfo);
try {
// ...
}
finally {
objectThreadLocal.remove();
}
\7. 【強制】高并發(fā)時,同步調(diào)用應該去考量鎖的性能損耗。能用無鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖;能鎖區(qū)塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
\8. 【強制】對多個資源、數(shù)據(jù)庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。
\9. 【強制】在使用阻塞等待獲取鎖的方式中,必須在 try 代碼塊之外,并且在加鎖方法與 try 代碼塊之間沒有任何可能拋出異常的方法調(diào)用,避免加鎖成功后,在 finally 中無法解鎖。
Lock lock = new XxxLock();
// ...
lock.lock();
try {
doSomething();
doOthers();
}
finally {
lock.unlock();
}
Lock lock = new XxxLock();
// ...
try {
// 如果此處拋出異常,則直接執(zhí)行 finally 代碼塊
doSomething();
// 無論加鎖是否成功,finally 代碼塊都會執(zhí)行
lock.lock();
doOthers();
}
finally {
lock.unlock();
}
\10. 【強制】在使用嘗試機制來獲取鎖的方式中,進入業(yè)務代碼塊之前,必須先判斷當前線程是否持有鎖。鎖的釋放規(guī)則與鎖的阻塞等待方式相同。
Lock lock = new XxxLock();
// ...
boolean isLocked = lock.tryLock();
if (isLocked) {
try {
doSomething();
doOthers();
}
finally {
lock.unlock();
}
}
11.【強制】并發(fā)修改同一記錄時,避免更新丟失,需要加鎖。要么在應用層加鎖,要么在緩存加鎖,要么在數(shù)據(jù)庫層使用樂觀鎖,使用 version 作為更新依據(jù)。
12.【強制】多線程并行處理定時任務時,Timer 運行多個 TimeTask 時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用 ScheduledExecutorService 則沒有這個問題。
13.【推薦】資金相關(guān)的金融敏感信息,使用悲觀鎖策略。
14.【推薦】使用 CountDownLatch 進行異步轉(zhuǎn)同步操作,每個線程退出前必須調(diào)用 countDown 方法,線程執(zhí)行代碼注意 catch 異常,確保 countDown 方法被執(zhí)行到,避免主線程無法執(zhí)行至await 方法,直到超時才返回結(jié)果。
15.【推薦】避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed 導致的性能下降。
16.【推薦】通過雙重檢查鎖(double-checked locking)(在并發(fā)場景下)存在延遲初始化的優(yōu)化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦解決方案中較為簡單一種(適用于 JDK5 及以上版本),將目標屬性聲明為 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...
}
17.【參考】volatile 解決多線程內(nèi)存不可見問題。對于一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。
AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
如果是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好(減少樂觀鎖的重試次數(shù))。18.【參考】HashMap 在容量不夠進行 resize 時由于高并發(fā)可能出現(xiàn)死鏈,導致 CPU 飆升,在開發(fā)過程中注意規(guī)避此風險。
19.【參考】ThreadLocal 對象使用 static 修飾,ThreadLocal 無法解決共享對象的更新問題。
更多建議: