讀阿里Java開(kāi)發(fā)手冊(cè)后的一些整理

之前阿里巴巴公眾號(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ù)的插入速度。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容