下載原版阿里JAVA開發手冊 【阿里巴巴Java開發手冊v1.2.0】
本文主要是對照阿里開發手冊,注釋自己在工作中運用情況。
本文內容:OOP 規約 、集合處理 、並發處理 、其他
本文難度系數為三星(★★★) 本文為第二篇
第一篇 點評阿里JAVA手冊之編程規約(命名風格、常量定義、代碼風格、控制語句、注釋規約)
第二篇 點評阿里JAVA手冊之編程規約(OOP 規約 、集合處理 、並發處理 、其他)
第三篇 點評阿里JAVA手冊之異常日志(異常處理 日志規約 )
第四篇 點評阿里JAVA手冊之MySQL數據庫 (建表規約、索引規約、SQL語句、ORM映射)
碼出高效、碼出質量。
代碼的字里行間流淌的是軟件生命中的血液,質量的提升是盡可能少踩坑,杜絕踩重復的坑,切實提升質量意識。另外,現代軟件架構都需要協同開發完成,高效協作即降低協同成本,提升溝通效率,所謂無規矩不成方圓,無規范不能協作。眾所周知,制訂交通法規表面上是要限制行車權,實際上是保障公眾的人身安全。試想如果沒有限速,沒有紅綠燈,誰還敢上路行駛。對軟件來說,適當的規范和標准絕不是消滅代碼內容的創造性、優雅性,而是限制過度個性化,以一種普遍認可的統一方式一起做事,提升協作效率。
(六) OOP 規約
1. 【強制】避免通過一個類的對象引用訪問此類的靜態變量或靜態方法,無謂增加編譯器解析成本,直接用類名來訪問即可。
【點評】規則好,已經遵循。
public class Student { public static final int MIN_AGE = 6; } Student st = new Student(); //錯誤 int minAge=st.MIN_AGE; //正確 int minAge=Student.MIN_AGE;
2. 【強制】所有的覆寫方法,必須加@Override 注解。
說明:getObject()與 get0bject()的問題。一個是字母的 O,一個是數字的 0,加@Override 可以准確判斷是否覆蓋成功。
另外,如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。
【點評】規則好,已經遵循。
3. 【強制】相同參數類型,相同業務含義,才可以使用 Java 的可變參數,避免使用 Object。 說明:可變參數必須放置在參數列表的最后。(提倡同學們盡量不用可變參數編程) 正例:public User getUsers(String type, Integer... ids) {...}
【點評】規則好,已經遵循。可變參數使用例子: String.format
4. 【強制】外部正在調用或者二方庫依賴的接口,不允許修改方法簽名,避免對接口調用方產生影響。接口過時必須加@Deprecated 注解,並清晰地說明采用的新接口或者新服務是什么。
【點評】規則好,已經遵循。
5. 【強制】不能使用過時的類或方法。 說明:java.net.URLDecoder 中的方法 decode(String encodeStr) 這個方法已經過時,應 該使用雙參數 decode(String source, String encode)。接口提供方既然明確是過時接口, 那么有義務同時提供新的接口;作為調用方來說,有義務去考證過時方法的新實現是什么。
【點評】規則好,已經遵循。如代碼中有未遵循的情況
如 java.net.URLEncoder url=new java.net.URLEncoder(); url.encode(s)
6. 【強制】Object 的 equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals。
正例: "test".equals(object);
反例: object.equals("test");
說明:推薦使用 java.util.Objects#equals (JDK7 引入的工具類)
【點評】規則好,已經遵循。未遵循推薦的工具類。
7. 【強制】所有的相同類型的包裝類對象之間值的比較,全部使用 equals 方法比較。
說明:對於 Integer var = ? 在-128 至 127 范圍內的賦值,Integer 對象是在 IntegerCache.cache 產生,會復用已有對象,這個區間內的 Integer 值可以直接使用==進行 判斷,但是這個區間之外的所有數據,都會在堆上產生,並不會復用已有對象,這是一個大坑, 推薦使用 equals 方法進行判斷。
【點評】規則好,已經遵循。 對於對象比較,都使用equals 方法比較。
8. 關於基本數據類型與包裝數據類型的使用標准如下:
1) 【強制】所有的 POJO 類屬性必須使用包裝數據類型。
2) 【強制】RPC 方法的返回值和參數必須使用包裝數據類型。
3) 【推薦】所有的局部變量使用基本數據類型。
說明:POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何 NPE 問題,或者入庫檢查,都由使用者來保證。
正例:數據庫的查詢結果可能是 null,因為自動拆箱,用基本數據類型接收有 NPE 風險。
反例:比如顯示成交總額漲跌情況,即正負 x%,x 為基本數據類型,調用的 RPC 服務,調用不成功時,返回的是默認值,頁面顯示:0%,這是不合理的,應該顯示成中划線-。所以包裝數據類型的 null 值,能夠表示額外的信息,如:遠程調用失敗,異常退出。
【點評】規則好,已經遵循。 方法內的變量(局部變量)使用基本類型。調用者進行java.lang.NullPointerException檢查。
9. 【強制】定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性默認值。
反例:POJO 類的 gmtCreate 默認值為 new Date();但是這個屬性在數據提取時並沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。
【點評】規則好,已經遵循。
10. 【強制】序列化類新增屬性時,請不要修改serialVersionUID 字段,避免反序列失敗;如果完全不兼容升級,避免反序列化混亂,那么請修改serialVersionUID 值。
說明:注意 serialVersionUID 不一致會拋出序列化運行時異常。
【點評】規則好,已經遵循。
11. 【強制】構造方法里面禁止加入任何業務邏輯,如果有初始化邏輯,請放在 init 方法中。
【點評】規則好,已經遵循。
12. 【強制】POJO 類必須寫 toString 方法。使用 IDE 的中工具:source> generate toString 時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。 說明:在方法執行拋出異常時,可以直接調用 POJO 的 toString()方法打印其屬性值,便於排查問題。
【點評】規則存疑,未遵循。
主要是根據需求來,當默認的toString()不能滿足你對”文本方式表示此對象“時,重寫toString(),例如bean類需要在重寫的toString 方法中組織自己想要顯示的當前對象的信息。
官方文檔如下:
public String toString()
返回該對象的字符串表示。通常,toString 方法會返回一個“以文本方式表示”此對象的字符串。結果應是一個簡明但易於讀懂的信息表達式。建議所有子類都重寫此方法。
Object 類的 toString 方法返回一個字符串,該字符串由類名(對象是該類的一個實例)、at 標記符“@”和此對象哈希碼的無符號十六進制表示組成。換句話說,該方法返回一個字符串,它的值等於:
getClass().getName() + '@' + Integer.toHexString(hashCode())
返回:
該對象的字符串表示形式。
13. 【推薦】使用索引訪問用 String 的 split 方法得到的數組時,需做最后一個分隔符后有無內容的檢查,否則會有拋 IndexOutOfBoundsException 的風險。 說明: String str = "a,b,c,,"; String[] ary = str.split(","); //預期大於 3,結果是 3 System.out.println(ary.length);
【點評】規則好,已經遵循。
14. 【推薦】當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一起, 便於閱讀。
【點評】規則好,已經遵循。
15. 【推薦】 類內方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter/setter 方法。 說明:公有方法是類的調用者和維護者最關心的方法,首屏展示最好;保護方法雖然只是子類 關心,也可能是“模板設計模式”下的核心方法;而私有方法外部一般不需要特別關心,是一個黑盒實現;因為方法信息價值較低,所有 Service 和 DAO 的 getter/setter 方法放在類體最后。
【點評】規則好,不過未嚴格遵循。
16. 【推薦】setter 方法中,參數名稱與類成員變量名稱一致,this.成員名 = 參數名。在 getter/setter 方法中,不要增加業務邏輯,增加排查問題的難度。
反例: public Integer getData() {
if (true) { return this.data + 100; }
else {
return this.data - 100; }
}
【點評】規則好,不過未嚴格遵循。
17. 【推薦】循環體內,字符串的連接方式,使用 StringBuilder 的 append 方法進行擴展。 說明:反編譯出的字節碼文件顯示每次循環都會 new 出一個 StringBuilder 對象,然后進行 append 操作,最后通過 toString 方法返回 String 對象,造成內存資源浪費。
反例: String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
【點評】規則好,嚴格遵循。
18. 【推薦】final 可以聲明類、成員變量、方法、以及本地變量,下列情況使用 final 關鍵字:
1) 不允許被繼承的類,如:String 類。
2) 不允許修改引用的域對象,如:POJO 類的域變量。
3) 不允許被重寫的方法,如:POJO 類的 setter 方法。
4) 不允許運行過程中重新賦值的局部變量。
5) 避免上下文重復使用一個變量,使用 final 描述可以強制重新定義一個變量,方便更好地進行重構。
【點評】規則好,嚴格遵循。
19. 【推薦】慎用 Object 的 clone 方法來拷貝對象。
說明:對象的 clone 方法默認是淺拷貝,若想實現深拷貝需要重寫 clone 方法實現屬性對象的拷貝。
【點評】規則好,嚴格遵循。BeanUtils.copyProperties(apiProcessor, this);來clone
20. 【推薦】類成員與方法訪問控制從嚴:
1) 如果不允許外部直接通過 new 來創建對象,那么構造方法必須是 private。
2) 工具類不允許有 public 或 default 構造方法。
3) 類非 static 成員變量並且與子類共享,必須是 protected。
4) 類非 static 成員變量並且僅在本類使用,必須是 private。
5) 類 static 成員變量如果僅在本類使用,必須是 private。
6) 若是 static 成員變量,必須考慮是否為 final。
7) 類成員方法只供類內部調用,必須是 private。
8) 類成員方法只對繼承類公開,那么限制為 protected。 說明:任何類、方法、參數、變量,嚴控訪問范圍。過於寬泛的訪問范圍,不利於模塊解耦。 思考:如果是一個 private 的方法,想刪除就刪除,可是一個 public 的 service 方法,或者 一個 public 的成員變量,刪除一下,不得手心冒點汗嗎?變量像自己的小孩,盡量在自己的 視線內,變量作用域太大,如果無限制的到處跑,那么你會擔心的。
【點評】規則好,嚴格遵循。
(七) 集合處理
1. 【強制】關於 hashCode 和 equals 的處理,遵循如下規則:
1) 只要重寫equals,就必須重寫hashCode。
2) 因為 Set 存儲的是不重復的對象,依據hashCode 和 equals 進行判斷,所以 Set 存儲的對象必須重寫這兩個方法。
3) 如果自定義對象做為 Map 的鍵,那么必須重寫 hashCode 和 equals。
說明:String 重寫hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對象作為 key 來使用。
【點評】規則好,嚴格遵循。
2. 【強制】 ArrayList的subList結果不可強轉成ArrayList,否則會拋出ClassCastException 異常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
說明:subList 返回的是 ArrayList 的內部類 SubList,並不是 ArrayList ,而是 ArrayList 的一個視圖,對於 SubList 子列表的所有操作最終會反映到原列表上。
【點評】規則好,嚴格遵循。
3. 【強制】 在 subList 場景中,高度注意對原集合元素個數的修改,會導致子列表的遍歷、增加、刪除均產生 ConcurrentModificationException 異常。
【點評】規則好,嚴格遵循。 多線程對同一HashMap的修改,也會出現ConcurrentModificationException
4. 【強制】使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全 一樣的數組,大小就是 list.size()。
說明:使用 toArray 帶參方法,入參分配的數組空間不夠大時,toArray 方法內部將重新分配 內存空間,並返回新數組地址;如果數組元素大於實際所需,下標為[ list.size() ]的數組 元素將被置為 null,其它數組元素保持原值,因此最好將方法入參數組大小定義與集合元素 個數一致。 正例:
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它 類型數組將出現 ClassCastException 錯誤。
【點評】規則好,嚴格遵循,代碼中很少用toArray 。
5. 【強制】使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方 法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。
說明:asList 的返回對象是一個 Arrays 內部類,並沒有實現集合的修改方法。Arrays.asList 體現的是適配器模式,只是轉換接口,后台的數據仍是數組。
String[] str = new String[] { "a", "b" }; List list = Arrays.asList(str);
第一種情況:list.add("c"); 運行時異常。 第二種情況:str[0] = "gujin"; 那么 list.get(0)也會隨之修改。
【點評】規則好,嚴格遵循
6. 【強制】泛型通配符<? extends T>來接收返回的數據,此寫法的泛型集合不能使用 add 方 法,而<? super T>不能使用 get 方法,做為接口調用賦值時易出錯。
說明:擴展說一下 PECS(Producer Extends Consumer Super)原則:
1)頻繁往外讀取內容 的,適合用上界 Extends。
2)經常往里插入的,適合用下界 Super。
【點評】規則好,嚴格遵循
Java 泛型
關鍵字說明
? 通配符類型
<? extends T> 表示類型的上界,表示參數化類型的可能是T 或是 T的子類
<? super T> 表示類型下界(Java Core中叫超類型限定),表示參數化類型是此類型的超類型(父類型),直至Object
extends 示例
static class Food{}
static class Fruit extends Food{}
static class Apple extends Fruit{}
static class RedApple extends Apple{}
List<? extends Fruit> flist = new ArrayList<Apple>();
// complie error:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // only work for null
List<? extends Frut> 表示 “具有任何從Fruit繼承類型的列表”,編譯器無法確定List所持有的類型,所以無法安全的向其中添加對象。可以添加null,因為null 可以表示任何類型。所以List 的add 方法不能添加任何有意義的元素,但是可以接受現有的子類型List<Apple> 賦值。
Fruit fruit = flist.get(0);
Apple apple = (Apple)flist.get(0);
由於,其中放置是從Fruit中繼承的類型,所以可以安全地取出Fruit類型。
flist.contains(new Fruit());
flist.contains(new Apple());
在使用Collection中的contains 方法時,接受Object 參數類型,可以不涉及任何通配符,編譯器也允許這么調用。
super 示例
List<? super Fruit> flist = new ArrayList<Fruit>();
flist.add(new Fruit());
flist.add(new Apple());
flist.add(new RedApple());
// compile error:
List<? super Fruit> flist = new ArrayList<Apple>();
List<? super Fruit> 表示“具有任何Fruit超類型的列表”,列表的類型至少是一個 Fruit 類型,因此可以安全的向其中添加Fruit 及其子類型。由於List<? super Fruit>中的類型可能是任何Fruit 的超類型,無法賦值為Fruit的子類型Apple的List<Apple>.
// compile error:
Fruit item = flist.get(0);
因為,List<? super Fruit>中的類型可能是任何Fruit 的超類型,所以編譯器無法確定get返回的對象類型是Fruit,還是Fruit的父類Food 或 Object.
小結
extends 可用於的返回類型限定,不能用於參數類型限定。
super 可用於參數類型限定,不能用於返回類型限定。
>帶有super超類型限定的通配符可以向泛型對易用寫入,帶有extends子類型限定的通配符可以向泛型對象讀取。
7. 【強制】不要在 foreach 循環里進行元素的 remove/add 操作。
remove 元素請使用 Iterator 方式,如果並發操作,需要對 Iterator 對象加鎖。
正例: Iterator<String> it = a.iterator();
while (it.hasNext()) {
String temp = it.next();
if (刪除元素的條件) {
it.remove();
}
}
反例: List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if ("1".equals(temp)) {
a.remove(temp);
}
}
說明:以上代碼的執行結果肯定會出乎大家的意料,那么試一下把“1”換成“2”,會是同樣的結果嗎? ConcurrentModificationException
【點評】規則好,嚴格遵循
8. 【強制】 在 JDK7 版本及以上,Comparator 要滿足如下三個條件,不然 Arrays.sort, Collections.sort 會報 IllegalArgumentException 異常。
說明: 1) x,y 的比較結果和 y,x 的比較結果相反。
2) x>y,y>z,則 x>z。
3) x=y,則 x,z 比較結果和 y,z 比較結果相同。
反例:下例中沒有處理相等的情況,實際使用中可能會出現異常:
new Comparator<Student>() {
@Override public int compare(Student o1, Student o2)
{ return o1.getId() > o2.getId() ? 1 : -1;
}
};
【點評】規則好,嚴格遵循
9. 【推薦】集合初始化時,指定集合初始值大小。
說明:HashMap 使用 HashMap(int initialCapacity) 初始化,
正例:initialCapacity = (需要存儲的元素個數 / 負載因子) + 1。
注意負載因子(即loader factor)默認為 0.75,如果暫時無法確定初始值大小,
請設置為 16。
反例:HashMap 需要放置 1024 個元素,由於沒有設置容量初始大小,隨着元素不斷增加,容量 7 次被迫擴大,resize 需要重建 hash 表,嚴重影響性能。
【點評】規則好,未嚴格遵循。在影響性能的情況下需要嚴格遵循
10. 【推薦】使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷。
說明:keySet 其實是遍歷了 2 次,一次是轉為 Iterator 對象,另一次是從 hashMap 中取出 key 所對應的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效 率更高。
如果是 JDK8,使用 Map.foreach 方法。
正例:values()返回的是 V 值集合,是一個 list 集合對象;
keySet()返回的是 K 值集合,是 一個 Set 集合對象;entrySet()返回的是 K-V 值組合集合。
【點評】規則好,嚴格遵循
第一種:
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}
效率高,以后一定要使用此種方式!
第二種:
Map map = new HashMap();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}
效率低,以后盡量少使用!
11. 【推薦】高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格:
集合類 |
Key |
Value |
Super Class |
說明 |
Hashtable |
不允許為null |
不允許為null |
Dictionary |
線程安全 |
ConcurrentHashMap |
不允許為 null |
不允許為 null |
AbstractMap |
分段鎖技術 |
TreeMap |
不允許為 null |
允許為 null |
AbstractMap |
線程不安全 |
HashMap |
允許為 null |
允許為 null |
AbstractMap |
線程不安全 |
反例: 由於 HashMap 的干擾,很多人認為 ConcurrentHashMap 是可以置入 null 值,而事實上, 存儲 null 值時會拋出 NPE 異常。
【點評】規則好,嚴格遵循
12. 【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。 說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是一定的。如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort。
【點評】規則好,嚴格遵循
13. 【參考】利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List 的 contains 方法進行遍歷、對比、去重操作。
【點評】規則好,嚴格遵循
(八) 並發處理
1. 【強制】獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。 說明:資源驅動類、工具類、單例工廠類都需要注
【點評】規則好,嚴格遵循意。
2. 【強制】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例: public class TimerTaskThread extends Thread {
public TimerTaskThread() { super.setName("TimerTaskThread"); ... }
【點評】規則存疑,匿名類呢?
3. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 說明:使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資
源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者 “過度切換”的問題。
【點評】規則好,未嚴格遵循。需要根據實際應用場景而定。
線程池模型 ,線程需要經常重復創建用線程池。適用於單次任務執行時間較短,但並發訪問量高的情況。當處理線程數設置極大的時候和非線程池模型幾乎沒有差別
非線程池模型:適用於單次連接任務執行時間較長,並發量不高的情況。
4. 【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors 返回的線程池對象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool: 允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。
【點評】規則好,嚴格遵循。
分布式程序下面,更應該考慮自定義的線程池。
package com.test; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Java線程:線程池-自定義線程池 * * @author Administrator 2009-11-4 23:30:44 */ public class NewTest { public static void main(String[] args) { // 創建等待隊列 BlockingQueue bqueue = new ArrayBlockingQueue(20); // 創建一個單線程執行程序,它可安排在給定延遲后運行命令或者定期地執行。 ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 50, TimeUnit.MILLISECONDS, bqueue); // 創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口 Thread t1 = new MyThread("test1"); Thread t2 = new MyThread("test2"); Thread t3 = new MyThread("test3"); Thread t4 = new MyThread("test4"); Thread t5 = new MyThread("test5"); Thread t6 = new MyThread("test6"); Thread t7 = new MyThread(); // 將線程放入池中進行執行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); pool.execute(t6); pool.execute(t7); int count= pool.getActiveCount(); System.out.println(pool.isTerminated()); do{ System.out.println(count); try { Thread.sleep(500L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } count= pool.getActiveCount(); } while(count>0); System.out.println(pool.isTerminated()); pool.shutdown(); System.out.println(pool.isTerminating()); try { Thread.sleep(2000L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(pool.isTerminated()); } } class MyThread extends Thread { private String labelName; public MyThread() { } public MyThread(String labelName) { this.labelName=labelName; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " "+this.labelName+"正在執行。。。"); try { Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " "+this.labelName+" end"); } }
5. 【強制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為 static,必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils。亦推薦如下處理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
說明:如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter代替Simpledateformatter,
官方給出的解釋:simple beautiful strong immutable thread-safe。
【點評】規則好,嚴格遵循。 每次都實例化DateFormat。
6. 【強制】高並發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。 說明:盡可能使加鎖的代碼塊工作量盡可能的小,避免在鎖代碼塊中調用 RPC 方法。
【點評】規則好,嚴格遵循。
7. 【強制】對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造 成死鎖。 說明:線程一需要對表 A、B、C 依次全部加鎖后才可以進行更新操作,那么線程二的加鎖順序 也必須是 A、B、C,否則可能出現死鎖。
【點評】規則好,嚴格遵循。
8. 【強制】並發修改同一記錄時,避免更新丟失,需要加鎖。要么在應用層加鎖,要么在緩存加 鎖,要么在數據庫層使用樂觀鎖,使用 version 作為更新依據。 說明:如果每次訪問沖突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次 數不得小於 3 次。
【點評】規則好,嚴格遵循。更好的方式是無鎖編程 。表結構如下
Id |
Name |
Count 庫存 |
1 |
火腿 |
50 |
2 |
雨傘 |
40 |
用update table set count=?-1 where id=2 and count=? ?為傳入參數。這樣可以無鎖編程。
或者update table set count=?-1 where id=2 and count>0 防止超賣。
9. 【強制】多線程並行處理定時任務時,Timer 運行多個 TimeTask 時,只要其中之一沒有捕獲 拋出的異常,其它任務便會自動終止運行,使用 ScheduledExecutorService 則沒有這個問題。
【點評】規則好,嚴格遵循。
10. 【推薦】使用 CountDownLatch 進行異步轉同步操作,每個線程退出前必須調用 countDown 方法,線程執行代碼注意 catch 異常,確保 countDown 方法可以執行,避免主線程無法執行 至 await 方法,直到超時才返回結果。 說明:注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。
【點評】規則好,嚴格遵循。
11. 【推薦】避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed 導致的性能下降。
說明:Random 實例包括 java.util.Random 的實例或者 Math.random()的方式。 正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要編碼保 證每個線程持有一個實例。
【點評】規則好,嚴格遵循。
12. 【推薦】在並發場景下,通過雙重檢查鎖(double-checked locking)實現延遲初始化的優 化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦問 題解決方案中較為簡單一種(適用於 JDK5 及以上版本),將目標屬性聲明為 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... }
【點評】規則好,嚴格遵循。
public class Singleton { private static volatile Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if(instance == null) { synchronized(Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } }
13. 【參考】volatile 解決多線程內存不可見問題。對於一寫多讀,是可以解決變量同步問題, 但是如果多寫,同樣無法解決線程安全問題。如果是 count++操作,使用如下類實現: AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推 薦使用 LongAdder 對象,比 AtomicLong 性能更好(減少樂觀鎖的重試次數)。
【點評】規則好,嚴格遵循。
14. 【參考】 HashMap 在容量不夠進行 resize 時由於高並發可能出現死鏈,導致 CPU 飆升,在 開發過程中可以使用其它數據結構或加鎖來規避此風險。
【點評】規則好,嚴格遵循。
15. 【參考】ThreadLocal 無法解決共享對象的更新問題,ThreadLocal 對象建議使用 static 修飾。這個變量是針對一個線程內所有操作共有的,所以設置為靜態變量,所有此類實例共享 此靜態變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象(只 要是這個線程內定義的)都可以操控這個變量。
【點評】規則好,嚴格遵循。
(九) 其它
1. 【強制】在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。 說明:不要在方法體內定義:Pattern pattern = Pattern.compile(規則);
【點評】規則好,需嚴格遵循。
2. 【強制】velocity 調用 POJO 類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按 規范調用 POJO 的 getXxx(),如果是 boolean 基本數據類型變量(boolean 命名不需要加 is 前綴),會自動調用 isXxx()方法。 說明:注意如果是 Boolean 包裝類對象,優先調用 getXxx()的方法。
【點評】規則好,需嚴格遵循。
3. 【強制】后台輸送給頁面的變量必須加$!{var}——中間的感嘆號。 說明:如果 var=null 或者不存在,那么${var}會直接顯示在頁面上。
【點評】規則好,需嚴格遵循。
4. 【強制】注意 Math.random() 這個方法返回是 double 類型,注意取值的范圍 0≤x<1(能夠 取到零值,注意除零異常),如果想獲取整數類型的隨機數,不要將 x 放大 10 的若干倍然后取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。
【點評】規則好,嚴格遵循。
5. 【強制】獲取當前毫秒數 System.currentTimeMillis(); 而不是 new Date().getTime(); 說明:如果想獲取更加精確的納秒級時間值,使用 System.nanoTime()的方式。 在 JDK8 中, 針對統計時間等場景,推薦使用 Instant 類。
【點評】規則好,嚴格遵循。
6. 【推薦】不要在視圖模板中加入任何復雜的邏輯。 說明:根據 MVC 理論,視圖的職責是展示,不要搶模型和控制器的活。
【點評】規則好,嚴格遵循。
7. 【推薦】任何數據結構的構造或初始化,都應指定大小,避免數據結構無限增長吃光內存。
【點評】規則好,未嚴格遵循。應考慮使用頻率等。
8. 【推薦】對於“明確停止使用的代碼和配置”,如方法、變量、類、配置文件、動態配置屬性 等要堅決從程序中清理出去,避免造成過多垃圾。
【點評】規則好,嚴格遵循。