天天看點

點評阿裡JAVA手冊之程式設計規約(OOP 規約 、集合處理 、并發處理 、其他)

 下載下傳原版阿裡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>.

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();

}

效率高,以後一定要使用此種方式!

第二種:

Iterator iter = map.keySet().iterator();

    Object key = iter.next();

    Object val = map.get(key);

效率低,以後盡量少使用!

11. 【推薦】高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格:

集合類 Key Value Super Class 說明
Hashtable 不允許為null Dictionary 線程安全
ConcurrentHashMap 不允許為 null AbstractMap 分段鎖技術
TreeMap 允許為 null 線程不安全
HashMap

    反例: 由于 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. 【推薦】對于“明确停止使用的代碼和配置”,如方法、變量、類、配置檔案、動态配置屬性 等要堅決從程式中清理出去,避免造成過多垃圾。