之前阿里巴巴公眾號(hào)推送過(guò)一條消息,內(nèi)容是阿里內(nèi)部整理的Java開(kāi)發(fā)手冊(cè)文檔,全篇讀完以后,整理出一份覺(jué)得可能對(duì)大家有所幫助的信息~。
1.POJO 類(lèi)中布爾類(lèi)型的變量,都不要加 is,否則部分框架解析會(huì)引起序列化錯(cuò)誤。
反例:定義為基本數(shù)據(jù)類(lèi)型boolean isSuccess;的屬性,它的方法也是isSuccess(),RPC 框架在反向解析的時(shí)候,“以為”對(duì)應(yīng)的屬性名稱(chēng)是 success,導(dǎo)致屬性獲取不到,進(jìn)而拋出異常。
2.包名統(tǒng)一使用小寫(xiě),點(diǎn)分隔符之間有且僅有一個(gè)自然語(yǔ)義的英語(yǔ)單詞。包名統(tǒng)一使用 單數(shù)形式,但是類(lèi)名如果有復(fù)數(shù)含義,類(lèi)名可以使用復(fù)數(shù)形式。
正例: 應(yīng)用工具類(lèi)包名為com.alibaba.open.util、類(lèi)名為MessageUtils(此規(guī)則參考 spring 的框架結(jié)構(gòu))
3.不要使用一個(gè)常量類(lèi)維護(hù)所有常量,應(yīng)該按常量功能進(jìn)行歸類(lèi),分開(kāi)維護(hù)。如:緩存 相關(guān)的常量放在類(lèi):CacheConsts 下;系統(tǒng)配置相關(guān)的常量放在類(lèi):ConfigConsts 下。
說(shuō)明:大而全的常量類(lèi),非得使用查找功能才能定位到修改的常量,不利于理解和維護(hù)
4.所有的相同類(lèi)型的包裝類(lèi)對(duì)象之間值的比較,全部使用 equals 方法比較。
說(shuō)明:對(duì)于Integer var=?在-128至127之間的賦值,Integer對(duì)象是在 IntegerCache.cache 產(chǎn)生,會(huì)復(fù)用已有對(duì)象,這個(gè)區(qū)間內(nèi)的 Integer 值可以直接使用==進(jìn)行 判斷,但是這個(gè)區(qū)間之外的所有數(shù)據(jù),都會(huì)在堆上產(chǎn)生,并不會(huì)復(fù)用已有對(duì)象,這是一個(gè)大坑, 推薦使用 equals 方法進(jìn)行判斷。
5.所有的POJO類(lèi)屬性必須使用包裝數(shù)據(jù)類(lèi)型,RPC方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類(lèi)型,所有的局部變量【推薦】使用基本數(shù)據(jù)類(lèi)型
6.關(guān)于 hashCode 和 equals 的處理,遵循如下規(guī)則
- 只要重寫(xiě)equals,就必須重寫(xiě)hashCode。
- 因?yàn)镾et存儲(chǔ)的是不重復(fù)的對(duì)象,依據(jù)hashCode和equals進(jìn)行判斷,所以Set存儲(chǔ)的 對(duì)象必須重寫(xiě)這兩個(gè)方法。
- 如果自定義對(duì)象做為Map的鍵,那么必須重寫(xiě)hashCode和equals
- String 重寫(xiě)了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對(duì)象 作為 key 來(lái)使用。
7.不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請(qǐng)使用 Iterator方式,如果并發(fā)操作,需要對(duì) Iterator 對(duì)象加鎖。
8.使用 entrySet 遍歷 Map 類(lèi)集合 KV,而不是 keySet 方式進(jìn)行遍歷
說(shuō)明:keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對(duì)象,另一次是從 hashMap 中取出 key 所對(duì)應(yīng)的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效 率更高。如果是 JDK8,使用 Map.foreach 方法
9.高度注意 Map 類(lèi)集合 K/V 能不能存儲(chǔ) null 值的情況
| 集合類(lèi) | Key | Value | Super | 說(shuō)明 |
|---|---|---|---|---|
| Hashtable | 不允許為 null | 不允許為 null | Dictionary | 線(xiàn)程安全 |
| ConcurrentHashMap | 不允許為 null | 不允許為 null | AbstractMap | 分段鎖技術(shù) |
| TreeMap | 不允許為 null | 允許為 null | AbstractMap | 線(xiàn)程不安全 |
| HashMap | 允許為 null | 允許為 null | AbstractMap | 線(xiàn)程不安全 |
反例: 由于 HashMap 的干擾,很多人認(rèn)為 ConcurrentHashMap 是可以置入 null 值,注意存儲(chǔ) null 值時(shí)會(huì)拋出 NPE 異常
10.獲取單例對(duì)象需要保證線(xiàn)程安全,其中的方法也要保證線(xiàn)程安全
資源驅(qū)動(dòng)類(lèi)、工具類(lèi)、單例工廠(chǎng)類(lèi)都需要注意。
11.線(xiàn)程池不允許使用 Executors 去創(chuàng)建,而是通過(guò) ThreadPoolExecutor 的方式,這樣 的處理方式讓寫(xiě)的同學(xué)更加明確線(xiàn)程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
Executors 返回的線(xiàn)程池對(duì)象的弊端如下
- FixedThreadPool 和 SingleThreadPool:允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM。
- CachedThreadPool 和 ScheduledThreadPool:允許的創(chuàng)建線(xiàn)程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線(xiàn)程,從而導(dǎo)致 OOM。
12.SimpleDateFormat 是線(xiàn)程不安全的類(lèi),一般不要定義為static變量,如果定義為static,必須加鎖,或者使用 DateUtils 工具類(lèi)。
正例:注意線(xiàn)程安全,使用 DateUtils。亦推薦如下處理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
如果是 JDK8 的應(yīng)用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 Simpledateformatter,官方給出的解釋:simple beautiful strong immutable thread-safe。
13.高并發(fā)時(shí),同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無(wú)鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖;能 鎖區(qū)塊,就不要鎖整個(gè)方法體;能用對(duì)象鎖,就不要用類(lèi)鎖。
14.對(duì)多個(gè)資源、數(shù)據(jù)庫(kù)表、對(duì)象同時(shí)加鎖時(shí),需要保持一致的加鎖順序,否則可能會(huì)造 成死鎖。
線(xiàn)程一需要對(duì)表 A、B、C 依次全部加鎖后才可以進(jìn)行更新操作,那么線(xiàn)程二的加鎖順序 也必須是 A、B、C,否則可能出現(xiàn)死鎖。
15.并發(fā)修改同一記錄時(shí),避免更新丟失,要么在應(yīng)用層加鎖,要么在緩存加鎖,要么在 數(shù)據(jù)庫(kù)層使用樂(lè)觀(guān)鎖,使用 version 作為更新依據(jù)
如果每次訪(fǎng)問(wèn)沖突概率小于 20%,推薦使用樂(lè)觀(guān)鎖,否則使用悲觀(guān)鎖。樂(lè)觀(guān)鎖的重試次 數(shù)不得小于 3 次。
16.多線(xiàn)程并行處理定時(shí)任務(wù)時(shí),Timer 運(yùn)行多個(gè) TimeTask 時(shí),只要其中之一沒(méi)有捕獲 拋出的異常,其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,使用 ScheduledExecutorService 則沒(méi)有這個(gè)問(wèn)題
17.使用 CountDownLatch 進(jìn)行異步轉(zhuǎn)同步操作,每個(gè)線(xiàn)程退出前必須調(diào)用 countDown方法,線(xiàn)程執(zhí)行代碼注意 catch 異常,確保 countDown 方法可以執(zhí)行,避免主線(xiàn)程無(wú)法執(zhí)行 至 countDown 方法,直到超時(shí)才返回結(jié)果。
說(shuō)明:注意,子線(xiàn)程拋出異常堆棧,不能在主線(xiàn)程 try-catch 到。
18.避免 Random 實(shí)例被多線(xiàn)程使用,雖然共享該實(shí)例是線(xiàn)程安全的,但會(huì)因競(jìng)爭(zhēng)同一 seed 導(dǎo)致的性能下降。
在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每個(gè) 線(xiàn)程一個(gè)實(shí)例。
19.通過(guò)雙重檢查鎖(double-checked locking)(在并發(fā)場(chǎng)景)實(shí)現(xiàn)延遲初始化的優(yōu) 化問(wèn)題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦問(wèn)題 解決方案中較為簡(jiǎn)單一種(適用于 JDK5 及以上版本),將目標(biāo)屬性聲明為 volatile 型
反例:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}
20.volatile 解決多線(xiàn)程內(nèi)存不可見(jiàn)問(wèn)題。對(duì)于一寫(xiě)多讀,是可以解決變量同步問(wèn)題, 但是如果多寫(xiě),同樣無(wú)法解決線(xiàn)程安全問(wèn)題。如果是 count++操作,使用如下類(lèi)實(shí)現(xiàn)
AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推 薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂(lè)觀(guān)鎖的重試次數(shù))。
21.HashMap 在容量不夠進(jìn)行 resize 時(shí)由于高并發(fā)可能出現(xiàn)死鏈,導(dǎo)致 CPU 飆升,在 開(kāi)發(fā)過(guò)程中注意規(guī)避此風(fēng)險(xiǎn)。
22.ThreadLocal 無(wú)法解決共享對(duì)象的更新問(wèn)題,ThreadLocal 對(duì)象建議使用 static 修飾。這個(gè)變量是針對(duì)一個(gè)線(xiàn)程內(nèi)所有操作共有的,所以設(shè)置為靜態(tài)變量,所有此類(lèi)實(shí)例共享 此靜態(tài)變量 ,也就是說(shuō)在類(lèi)第一次被使用時(shí)裝載,只分配一塊存儲(chǔ)空間,所有此類(lèi)的對(duì)象(只 要是這個(gè)線(xiàn)程內(nèi)定義的)都可以操控這個(gè)變量。
23.不能在 finally 塊中使用 return,finally 塊中的 return 返回后方法結(jié)束執(zhí)行,不 會(huì)再執(zhí)行 try 塊中的 return 語(yǔ)句
24.應(yīng)用中不可直接使用日志系統(tǒng)(Log4j、Logback)中的 API,而應(yīng)依賴(lài)使用日志框架SLF4J 中的 API,使用門(mén)面模式的日志框架,有利于維護(hù)和各個(gè)類(lèi)的日志處理方式統(tǒng)一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
25.對(duì) trace/debug/info 級(jí)別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
說(shuō)明:logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 如果日志級(jí)別是 warn,上述日志不會(huì)打印,但是會(huì)執(zhí)行字符串拼接操作,如果 symbol 是對(duì)象, 會(huì)執(zhí)行 toString()方法,浪費(fèi)了系統(tǒng)資源,執(zhí)行了上述操作,最終日志卻沒(méi)有打印。
正例:(條件)
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
}
正例:(占位符)
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
26.避免重復(fù)打印日志,浪費(fèi)磁盤(pán)空間,務(wù)必在 log4j.xml 中設(shè)置 additivity=false。
正例:
<logger name="com.taobao.dubbo.config" additivity="false">
27.異常信息應(yīng)該包括兩類(lèi)信息:案發(fā)現(xiàn)場(chǎng)信息和異常堆棧信息。如果不處理,那么往上拋。
正例:
logger.error(各類(lèi)參數(shù)或者對(duì)象toString + "_" + e.getMessage(), e);
28.建表時(shí)小數(shù)類(lèi)型為 decimal,禁止使用 float 和 double。
float 和 double 在存儲(chǔ)的時(shí)候,存在精度損失的問(wèn)題,很可能在值的比較時(shí),得到不 正確的結(jié)果。如果存儲(chǔ)的數(shù)據(jù)范圍超過(guò) decimal 的范圍,建議將數(shù)據(jù)拆成整數(shù)和小數(shù)分開(kāi)存儲(chǔ)。
29.varchar 是可變長(zhǎng)字符串,不預(yù)先分配存儲(chǔ)空間,長(zhǎng)度不要超過(guò) 5000,如果存儲(chǔ)長(zhǎng) 度大于此值,定義字段類(lèi)型為 text,獨(dú)立出來(lái)一張表,用主鍵來(lái)對(duì)應(yīng),避免影響其它字段索引效率。
30.如果有 order by 的場(chǎng)景,請(qǐng)注意利用索引的有序性。order by 最后的字段是組合 索引的一部分,并且放在索引組合順序的最后,避免出現(xiàn) file_sort 的情況,影響查詢(xún)性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引中有范圍查找,那么索引有序性無(wú)法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 無(wú)法排序。
31.利用延遲關(guān)聯(lián)或者子查詢(xún)優(yōu)化超多分頁(yè)場(chǎng)景。
說(shuō)明:MySQL 并不是跳過(guò) offset 行,而是取 offset+N 行,然后返回放棄前 offset 行,返回 N 行,那當(dāng) offset 特別大的時(shí)候,效率就非常的低下,要么控制返回的總頁(yè)數(shù),要么對(duì)超過(guò) 特定閾值的頁(yè)數(shù)進(jìn)行 SQL 改寫(xiě)。
正例:先快速定位需要獲取的 id 段,然后再關(guān)聯(lián):
SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id
32.SQL 性能優(yōu)化的目標(biāo):至少要達(dá)到 range 級(jí)別,要求是 ref 級(jí)別,如果可以是 consts 最好。
- 1.consts 單表中最多只有一個(gè)匹配行(主鍵或者唯一索引),在優(yōu)化階段即可讀取到數(shù)據(jù)。
- 2.ref 指的是使用普通的索引(normal index)。
- 3.range 對(duì)索引進(jìn)行范圍檢索。
反例:explain 表的結(jié)果,type=index,索引物理文件全掃描,速度非常慢,這個(gè) index 級(jí) 別比較 range 還低,與全表掃描是小巫見(jiàn)大巫
33.建組合索引的時(shí)候,區(qū)分度最高的在最左邊
如果 where a=? and b=? ,a 列的幾乎接近于唯一值,那么只需要單建 idx_a 索引即 可。
存在非等號(hào)和等號(hào)混合判斷條件時(shí),在建索引時(shí),請(qǐng)把等號(hào)條件的列前置。如:where a>? and b=? 那么即使 a 的區(qū)分度更高,也必須把 b 放在索引的最前列。
34.不要使用 count(列名)或 count(常量)來(lái)替代 count(*),count(*)就是 SQL92 定義 的標(biāo)準(zhǔn)統(tǒng)計(jì)行數(shù)的語(yǔ)法,跟數(shù)據(jù)庫(kù)無(wú)關(guān),跟 NULL 和非 NULL 無(wú)關(guān)。
count(*)會(huì)統(tǒng)計(jì)值為 NULL 的行,而 count(列名)不會(huì)統(tǒng)計(jì)此列為 NULL 值的行。
35.count(distinct col) 計(jì)算該列除 NULL 之外的不重復(fù)數(shù)量。注意 count(distinct col1, col2) 如果其中一列全為NULL,那么即使另一列有不同的值,也返回為0。
36.當(dāng)某一列的值全是 NULL 時(shí),count(col)的返回結(jié)果為 0,但 sum(col)的返回結(jié)果為 NULL,因此使用 sum()時(shí)需注意 NPE 問(wèn)題。
正例:可以使用如下方式來(lái)避免sum的NPE問(wèn)題:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
37.使用 ISNULL()來(lái)判斷是否為 NULL 值。注意:NULL 與任何值的直接比較都為 NULL
38.不得使用外鍵與級(jí)聯(lián),一切外鍵概念必須在應(yīng)用層解決。
學(xué)生表中的 student_id 是主鍵,那么成績(jī)表中的 student_id 則為外鍵。 如果更新學(xué)生表中的 student_id,同時(shí)觸發(fā)成績(jī)表中的 student_id 更新,則為級(jí)聯(lián)更新。 外鍵與級(jí)聯(lián)更新適用于單機(jī)低并發(fā),不適合分布式、高并發(fā)集群;級(jí)聯(lián)更新是強(qiáng)阻塞,存在數(shù)據(jù)庫(kù)更新風(fēng)暴的風(fēng)險(xiǎn);外鍵影響數(shù)據(jù)庫(kù)的插入速度。