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

之前看過阿里內(nèi)部整理的Java開發(fā)手冊(cè)文檔,全篇讀完以后,整理出一份覺得可能對(duì)大家有所幫助的信息~。

1.POJO 類中布爾類型的變量,都不要加 is,否則部分框架解析會(huì)引起序列化錯(cuò)誤。

反例:定義為基本數(shù)據(jù)類型boolean isSuccess;的屬性,它的方法也是isSuccess(),RPC 框架在反向解析的時(shí)候,“以為”對(duì)應(yīng)的屬性名稱是 success,導(dǎo)致屬性獲取不到,進(jìn)而拋出異常。

2.包名統(tǒng)一使用小寫,點(diǎn)分隔符之間有且僅有一個(gè)自然語義的英語單詞。包名統(tǒng)一使用 單數(shù)形式,但是類名如果有復(fù)數(shù)含義,類名可以使用復(fù)數(shù)形式。

正例: 應(yīng)用工具類包名為com.alibaba.open.util、類名為MessageUtils(此規(guī)則參考 spring 的框架結(jié)構(gòu))

3.不要使用一個(gè)常量類維護(hù)所有常量,應(yīng)該按常量功能進(jìn)行歸類,分開維護(hù)。如:緩存 相關(guān)的常量放在類:CacheConsts 下;系統(tǒng)配置相關(guān)的常量放在類:ConfigConsts 下。

說明:大而全的常量類,非得使用查找功能才能定位到修改的常量,不利于理解和維護(hù)

4.所有的相同類型的包裝類對(duì)象之間值的比較,全部使用 equals 方法比較。

說明:對(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類屬性必須使用包裝數(shù)據(jù)類型,RPC方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型,所有的局部變量【推薦】使用基本數(shù)據(jù)類型。

6.關(guān)于 hashCode 和 equals 的處理,遵循如下規(guī)則。

只要重寫equals,就必須重寫hashCode。

因?yàn)镾et存儲(chǔ)的是不重復(fù)的對(duì)象,依據(jù)hashCode和equals進(jìn)行判斷,所以Set存儲(chǔ)的 對(duì)象必須重寫這兩個(gè)方法。

如果自定義對(duì)象做為Map的鍵,那么必須重寫hashCode和equals

String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對(duì)象 作為 key 來使用。

7.不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請(qǐng)使用 Iterator方式,如果并發(fā)操作,需要對(duì) Iterator 對(duì)象加鎖。

8.使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進(jìn)行遍歷。

說明:keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對(duì)象,另一次是從 hashMap 中取出 key 所對(duì)應(yīng)的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效 率更高。如果是 JDK8,使用 Map.foreach 方法

9.高度注意 Map 類集合 K/V 能不能存儲(chǔ) null 值的情況。

反例: 由于 HashMap 的干擾,很多人認(rèn)為 ConcurrentHashMap 是可以置入 null 值,注意存儲(chǔ) null 值時(shí)會(huì)拋出 NPE 異常

10.獲取單例對(duì)象需要保證線程安全,其中的方法也要保證線程安全。

資源驅(qū)動(dòng)類、工具類、單例工廠類都需要注意。

11.線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣 的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。

Executors 返回的線程池對(duì)象的弊端如下

FixedThreadPool 和 SingleThreadPool:允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM。

CachedThreadPool 和 ScheduledThreadPool:允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線程,從而導(dǎo)致 OOM。

12.SimpleDateFormat 是線程不安全的類,一般不要定義為static變量,如果定義為static,必須加鎖,或者使用 DateUtils 工具類。

正例:注意線程安全,使用 DateUtils。亦推薦如下處理:

如果是 JDK8 的應(yīng)用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 Simpledateformatter,官方給出的解釋:simple beautiful strong immutable thread-safe。

13.高并發(fā)時(shí),同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖;能 鎖區(qū)塊,就不要鎖整個(gè)方法體;能用對(duì)象鎖,就不要用類鎖。

14.對(duì)多個(gè)資源、數(shù)據(jù)庫表、對(duì)象同時(shí)加鎖時(shí),需要保持一致的加鎖順序,否則可能會(huì)造 成死鎖。

線程一需要對(duì)表 A、B、C 依次全部加鎖后才可以進(jìn)行更新操作,那么線程二的加鎖順序 也必須是 A、B、C,否則可能出現(xiàn)死鎖。

15.并發(fā)修改同一記錄時(shí),避免更新丟失,要么在應(yīng)用層加鎖,要么在緩存加鎖,要么在 數(shù)據(jù)庫層使用樂觀鎖,使用 version 作為更新依據(jù)。

如果每次訪問沖突概率小于 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次 數(shù)不得小于 3 次。

16.多線程并行處理定時(shí)任務(wù)時(shí),Timer 運(yùn)行多個(gè) TimeTask 時(shí),只要其中之一沒有捕獲 拋出的異常,其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,使用 ScheduledExecutorService 則沒有這個(gè)問題。

17.使用 CountDownLatch 進(jìn)行異步轉(zhuǎn)同步操作,每個(gè)線程退出前必須調(diào)用 countDown方法,線程執(zhí)行代碼注意 catch 異常,確保 countDown 方法可以執(zhí)行,避免主線程無法執(zhí)行 至 countDown 方法,直到超時(shí)才返回結(jié)果。

說明:注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。

18.避免 Random 實(shí)例被多線程使用,雖然共享該實(shí)例是線程安全的,但會(huì)因競(jìng)爭(zhēng)同一 seed 導(dǎo)致的性能下降。

在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每個(gè) 線程一個(gè)實(shí)例。

19.通過雙重檢查鎖(double-checked locking)(在并發(fā)場(chǎng)景)實(shí)現(xiàn)延遲初始化的優(yōu) 化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦問題 解決方案中較為簡(jiǎn)單一種(適用于 JDK5 及以上版本),將目標(biāo)屬性聲明為 volatile 型。

反例:

20.volatile 解決多線程內(nèi)存不可見問題。對(duì)于一寫多讀,是可以解決變量同步問題, 但是如果多寫,同樣無法解決線程安全問題。如果是 count++操作,使用如下類實(shí)現(xiàn)。

AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推 薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂觀鎖的重試次數(shù))。

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

22.ThreadLocal 無法解決共享對(duì)象的更新問題,ThreadLocal 對(duì)象建議使用 static 修飾。這個(gè)變量是針對(duì)一個(gè)線程內(nèi)所有操作共有的,所以設(shè)置為靜態(tài)變量,所有此類實(shí)例共享 此靜態(tài)變量 ,也就是說在類第一次被使用時(shí)裝載,只分配一塊存儲(chǔ)空間,所有此類的對(duì)象(只 要是這個(gè)線程內(nèi)定義的)都可以操控這個(gè)變量。

23.不能在 finally 塊中使用 return,finally 塊中的 return 返回后方法結(jié)束執(zhí)行,不 會(huì)再執(zhí)行 try 塊中的 return 語句。

24.應(yīng)用中不可直接使用日志系統(tǒng)(Log4j、Logback)中的 API,而應(yīng)依賴使用日志框架SLF4J 中的 API,使用門面模式的日志框架,有利于維護(hù)和各個(gè)類的日志處理方式統(tǒng)一。

25.對(duì) trace/debug/info 級(jí)別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。

說明:logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 如果日志級(jí)別是 warn,上述日志不會(huì)打印,但是會(huì)執(zhí)行字符串拼接操作,如果 symbol 是對(duì)象, 會(huì)執(zhí)行 toString()方法,浪費(fèi)了系統(tǒng)資源,執(zhí)行了上述操作,最終日志卻沒有打印。

正例:(條件)

正例:(占位符)

26.避免重復(fù)打印日志,浪費(fèi)磁盤空間,務(wù)必在 log4j.xml 中設(shè)置 additivity=false。

正例:

27.異常信息應(yīng)該包括兩類信息:案發(fā)現(xiàn)場(chǎng)信息和異常堆棧信息。如果不處理,那么往上拋。

正例:

28.建表時(shí)小數(shù)類型為 decimal,禁止使用 float 和 double。

float 和 double 在存儲(chǔ)的時(shí)候,存在精度損失的問題,很可能在值的比較時(shí),得到不 正確的結(jié)果。如果存儲(chǔ)的數(shù)據(jù)范圍超過 decimal 的范圍,建議將數(shù)據(jù)拆成整數(shù)和小數(shù)分開存儲(chǔ)。

29.varchar 是可變長(zhǎng)字符串,不預(yù)先分配存儲(chǔ)空間,長(zhǎng)度不要超過 5000,如果存儲(chǔ)長(zhǎng) 度大于此值,定義字段類型為 text,獨(dú)立出來一張表,用主鍵來對(duì)應(yīng),避免影響其它字段索引效率。

30.如果有 order by 的場(chǎng)景,請(qǐng)注意利用索引的有序性。order by 最后的字段是組合 索引的一部分,并且放在索引組合順序的最后,避免出現(xiàn) file_sort 的情況,影響查詢性能。

正例:where a=? and b=? order by c; 索引:a_b_c

反例:索引中有范圍查找,那么索引有序性無法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 無法排序。

31.利用延遲關(guān)聯(lián)或者子查詢優(yōu)化超多分頁場(chǎng)景。

說明:MySQL 并不是跳過 offset 行,而是取 offset+N 行,然后返回放棄前 offset 行,返回 N 行,那當(dāng) offset 特別大的時(shí)候,效率就非常的低下,要么控制返回的總頁數(shù),要么對(duì)超過 特定閾值的頁數(shù)進(jìn)行 SQL 改寫。

正例:先快速定位需要獲取的 id 段,然后再關(guān)聯(lián):

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 還低,與全表掃描是小巫見大巫

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(常量)來替代 count(*),count(*)就是 SQL92 定義 的標(biāo)準(zhǔn)統(tǒng)計(jì)行數(shù)的語法,跟數(shù)據(jù)庫無關(guān),跟 NULL 和非 NULL 無關(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 問題。

正例:可以使用如下方式來避免sum的NPE問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;

37.使用 ISNULL()來判斷是否為 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ù)庫更新風(fēng)暴的風(fēng)險(xiǎn);外鍵影響數(shù)據(jù)庫的插入速度。


學(xué)習(xí)Java的同學(xué)注意了?。。?br>學(xué)習(xí)過程中遇到什么問題或者想獲取學(xué)習(xí)資源的話,歡迎加入Java學(xué)習(xí)交流群346942462,我們一起學(xué)Java!

最后編輯于
?著作權(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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