java開發手冊(重心篇 - 編程)——摘自《阿里巴巴Java開發手冊》

手冊

方法簽名:方法名(參數類型...)

NPE:NULL POINTER EXCEPTION

編程規約

命名風格

  1. 【強制】類名使用UpperCamelCase風格,但DO / BO / DTO / VO / AO / PO等情形例外。
    正例:ForceCode / UserDO / XmlService / TcpUdpDeal / TaPromotion / QrCode
    反例:forceCode / UserDo / XMLService / TCPUDPDeal / TAPromotion /QRCode
  2. 【強制】常量命名全部要大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。
    正例:MAX_STOCK_COUNT /CACHE_EXPIRED_TIME
    反例:MAX_COUNT / EXPIRED_TIME
  3. 【強制】抽象類名使用 Abstrac 或 Base 開頭;異常類名使用Exception結尾;測試類名以它要測試的類名開始,Test結尾。
  4. 【強制】POJO類中布爾類型的變量都不要加 is ,否則部分框架解析會引起序列化錯誤。
    說明:在“建表規約”第一條,表達 是與否 的值採用 is_xxx 的命名方式,所以需要在<resultMap>(字段關係映射)設置從is_xxx到Xxx的映射關係。
    反例:定義爲基本數據類型Boolean isDeleted;的屬性,它的方法名稱也是 isDeled(),RPC框架在反向解析的時候,“誤以爲”對應的屬性名稱是 deleted,導致屬性獲取不到拋出異常。
  5. 【強制】包名統一使用小寫,單數形式,類名有複數含義,則類名可以使用複數形式。
  6. 【推薦】如果模塊、接口、類、方法中使用了設計模式,應在命名時體現出具體模式。
    正例:public class OrderFactory; public class LoginProxy; public classResourceOberver; public class CakeDecorator;
  7. 【推薦】接口類中的方法和屬性不要加任何修飾符號(public 也不要加),保持代碼的簡潔性,並加上有效的額 Javadoc 註釋,儘量不要在接口中定義變量,如果一定要定義變量,必須是和接口方法相關的,並且是整個應用的基礎常量。
    正例:接口方法簽名:void commit();
               接口基礎常量:String COMPANY = "alibaba";(接口中默認public static final)
    反例:public abstract void commit();
    注意:JDK1.8 中接口允許有默認實現,default void commit(){System.out.println("默認實現");},實現類中不重寫此方法,則調用默認實現方法,若有重寫,則調用實現類重寫的方法。
  8. 【參考】枚舉類名建議帶上 Enum 後綴,枚舉成員名稱需要全部大寫,單詞用下劃線隔開。特殊的常量
    說明:枚舉其實就是特殊的常量類,且構造方法被默認強制爲私有。
    正例:類名:ProcessStatusEnum,成員名稱:SUCCESS / UNKNOW_REASON
  9. 【參考】各層命名規約:
    1)Service / DAO 層方法:
    獲取單個對象方法用 get 爲前綴。
    獲取多個對象方法用 list 爲前綴,複數結尾,如:listObjects。
    獲取統計值的方法用 count 爲前綴。
    插入的方法用 save / insert 爲前綴。
    刪除的方法用 remove / delete 爲前綴。
    修改的方法用 update 爲前綴。
    2)領域模型:
    數據對象:xxxDO,xxx爲數據表名。
    數據傳輸對象:xxxDTO,xxx爲業務領域相關的名稱。
    展示對象:xxxVO,xxx一般爲網頁名稱。
    POJO 是 DO / DTO / VO / BO 的統稱,禁止命名成 xxxPOJO

常量定義

  1. ...

代碼格式

  1. 【強制】在進行類型強制轉換時,右括號與強制轉化值之間不需要任何空格離開。
    正例:long first = 10000000000L;
               int second = (int)first + 2;
  2. 【單行字符不超過120個】,超出則需要換行,換行時遵循如下原則:
    1)第二行相對第一行縮進4個空格,從第三行開始,不在持續縮進。
    2)運算符與下文一起換行。
    3)方法調用的點符號與下文一起換行。
    4)方法調用中的多個參數需要換行時,在逗號後進行。
    5)在括號前不要換行。
  3. 【強制】IDE 的 text file encoding 設置爲 UTF-8;IDE 中文件的換行符使用 UNIX 格式,不要使用Windows 格式。
  4. 【推薦】單個方法的總行數不超過80行。
    說明:除註釋外的方法簽名、左右大括號、方法內代碼、空行回車及任何不可見字符的總行數不超過80行。
    正例:代碼邏輯分清紅花和綠葉,個性和共性,綠葉邏輯單獨成爲額外方法,使主幹代碼更加清晰;共性邏輯抽取成爲共性方法,便於複用和維護。
  5. 【推薦】不同邏輯、不同語義、不同業務的代碼之間插入一個空行分隔開來,以提升可讀性。(沒必要插入多個空行)

OOP規約

  1. 【強制】避免通過一個類的對象引用訪問此類的靜態成員變量或方法,造成無謂增加編譯器解析成本,直接用類名來訪問即可。
  2. 【強制】所有的複寫方法,必須加 @Override 註解。
  3. 【強制】相同參數類型,相同業務含義,纔可使用 Java 的可變參數避免使用Object。
    說明:可變參數必須放置在參數列表的最後(儘量不使用),【很多SQL拼接會使用】
    正例:public List<User> listUsers(String type,Long... ids) {...}
  4. 【強制】對外部正在調用或者二方依賴庫的接口,不允許修改方法簽名,以避免對接口調用方產生影響。若接口過時,必須加 @Deprecated 註解,並清晰說明採用的新街口或者新服務是什麼。
  5. 【強制】不能使用過時的方法和類。
    說明:java.net.URLDecoder 中的方法 decode(String encodeStr) 已經過時,應該使用雙參數 decode(String source, String encode)。接口提供方既然明確是過時接口,那麼有義務提供新接口;作爲調用方,有義務去考證過時方法的新實現是什麼。
  6. 【強制】Object 的 equals 方法容易拋出空指針異常,應使用常量或有值的對象來調用 equals。
    正例:"test".equals(object)
    反例:object.equals("test")
    說明:推薦使用 java.util.Objects#equals (JDK7引入的工具類)。
  7. 【強制】所有整型包裝類對象之間值的比較,全部使用 equals 。
    說明:對於 Integer var = ? 在 -128 ~ 127 範圍內的賦值,Integer 對象實在 IntegerCache.cache 中產生的,會複用已有對象,這個區間內的值可以直接用 “== ”進行判斷,但是這個區間以外的數據在上產生,並不會複用已有對象!
  8. 【強制】浮點數之間的等值判斷,基本數據類型不能用 “==”包裝數據不能用 equals 。
    說明:浮點數採用“尾數 + 階碼”的編碼方式,類似與科學計數法的“有效數字 + 指數”的表示方式。二進制無法精確表示大部分的十進制小數。
    反例:
    float a = 1.0f - 0.9f;
    float b = 0.9f - 0.8f;
    if (a == b) {
        // 預期進入
        //結局 false
    }
    Float x = Float.valueOf(a);
    Float y = Float.valueOf(b);
    if (x.equals(y)) {
        // false
    }
    

    正例:
    1)指定一個誤差範圍,兩個浮點數的差值在此範圍內,認爲相等。
    2)使用 BigDecimal 來定義,在進行浮點數的運算操作。

  9. 【強制】禁止使用構造方法 BigDecimal(double) 的方式把 double 的值轉化爲 BigDecimal 對象。
    說明:BigDecimal(double) 存在精度損失的風險,在精確計算或值比較的場景中可能會導致業務邏輯異常。
    如:BigDecimal b = new BigDecimal(0.1f);實際的存儲值爲:0.100000001490116119384765625。
    正例:優先推薦入參爲 String 的構造方法,或使用 BigDecimal 的 valueOf 方法,此方法內部執行了 Double 的 toString,而 Double 的 toString 按 double 實際能表達的精度對尾數進行了截斷。
    BigDecimal good1 = new BigDecimal("0.1");
    BigDecimal good2 = BigDecimal.valueOf(0.1);

  10. 關於基本數據類型與包裝數據類型的使用標準:
    1)【強制】所有的 POJO 類屬性必須使用包裝數據類型。
    2)【強制】RPC 方法的返回值和參數必須使用包裝數據類型。
    3)【推薦】所有的局部變量使用基本數據類型。
    說明:POJO 類屬性沒有初始值,是要提醒使用者在需要使用時,必須自己顯示的進行賦值,任何 NPE 問題,或者入庫檢查,都由使用者來保證。
    正例:數據庫的查詢結果可能是 null ,因爲自動拆箱,所以用基本數據類型接收有 NPE 風險
    反例:比如顯示成交總額漲跌的情況,即正負 x%,x 爲基本數據類型,調用的RPC服務在調用不成功時,返回的是默認值,頁面顯示爲 0%,這是不合理的,應該顯示成中畫線,所以包裝數據類型的 null 值,能夠表示額外的值,如遠程調用失敗,異常退出。

  11. 【強制】在定義 DO / DTO / VO 等 POJO類時,不要設定任何默認值。
    反例:POJO類的createTime默認值爲 new Date();但是這個屬性在數據提取時並沒有置入具體值,在更新其他字段時,又附帶更新了此字段,導致創建時間被修改成當前時間。

  12. 【強制】當序列化類新增屬性時,請不要修改 serialVersionUID 字段,以避免反序列化失敗;如果完全不兼容升級,避免反序列化混亂,那麼請修改 serialVersionUID 值。
    說明:注意 serialVersionUID 不一致會拋出序列化運行異常。

  13. 【強制】POJO 類必須寫 toString 方法,如果繼承了另一個 POJO類,加上 super.toString()。
    說明:便於排查問題查看屬性

  14. 【強制】禁止在 POJO 類中,同時存在對應屬性 xxx 的 isXxx() 和 getXxx() 方法。
    說明:框架在調用屬性 xxx 的提取方法時,並不能確定哪種方法一定是被優先調用到。

  15. 【推薦】當使用索引訪問用 String 的 split 方法得到的數組時,需在最後一個分隔符後做有無內容的檢查,否則會有拋 IndexOutOfBoundsException 的風險。(可先在字符串末尾加一個不包含分隔符的字符串,轉化爲數組後再截取)
    說明:

    String str = "a,b,c,,,";
    String[] ary = str.split(",");
    // 預期 6 ,實際 3
    System.out.println(ary.length);
  16. 【推薦】循環體內,字符串的拼接,使用 StringBuilder 的 append 方法進行擴展(本質也是如此)。
    說明:String 對象的字符串拼接,每次都會 new 一個 StringBuilder 對象,然後進行 append 操作,最後通過 toString 方法返回String。

    String str = "start";
    for (int i = 0; i < 100; i++) {
        str = str + "hello";
    }
    // 推薦做法 下
    StringBuilder sb = new StringBuilder(str);
    for (int i = 0; i < 100; i++) {
        sb.append("hello");
    }
    str = sb.toString();
  17. 【推薦】慎用 Object 的 clone 方法來拷貝對象。
    說明:對象的 clone 方法默認是淺拷貝,若想實現深拷貝,需要重寫 clone 方法來實現域對象的深度遍歷式拷貝。

  18. 【推薦】類成員與方法訪問控制從嚴
    1) 如果不允許外部直接通過 new 來創建對象,那麼構造方法必須限制爲 private。
    2) 工具類不允許有 public 或 default 構造方法。
    3) 類非 static 成員變量並且與子類共享,必須限制爲 protected;
    4) 類非 static 成員變量並且僅在本類使用,必須限制爲 private;
    5) 類 static 成員變量如果僅在本類使用,必須限制爲 private;
    6) 若是 static 成員變量,必須考慮是否爲 final;
    7) 類成員方法只供類內部調用,必須限制爲 private 。
    8) 類成員方法只對繼承類公開,限制爲 protected 。
    說明:對任何類、方法、參數、變量,嚴格控制訪問範圍。過於寬泛的訪問範圍,不利於模塊解耦。

集合處理

  1. 【強制】關於 hashCode 和 equals 的處理,遵循如下原則:
    1) 只要重寫 equals,就必須重寫 hashCode 。
    2) 因爲 Set 存儲的是不重複對象,依據 hashCode 和 equals 進行判斷,所以 Set 存儲的對象必須重寫這兩種方法。
    3)如果自定義對象作爲 Map 的鍵,那麼必須重寫 hashCode 和 equals 。
    說明:String 重寫了 hashCode 和 equals ,所以我們可以很愉快地將 String 對象作爲 key 來使用。
  2. 【強制】使用 Map 的方法 keySet() / values() / entrySet() 返回集合對象時,不可以對其添加元素,否則會拋出 UnsupportedOperationException。
  3. 【強制】Collections 類返回的對象,如 emptyList() / singletonList() 等都是 immutable list(不可變的),不可以對其添加或刪除元素。
    說明:Arrays.asList() 方法則是返回一個內部類 ArrayList,沒有重寫 add(),remove() 等方法,調用也會拋出 UnsupportedOperationException 異常。
    反例:某二方庫的方法中,如果查詢無結果,返回 Collections.emptyList() 空集合對象,調用方一旦進行了添加元素的操作,就會觸發 UnsupportedOperationException 異常。
  4. 【強制】ArrayList 的 subList 結果不可強制轉化爲 ArrayList,否則會拋 ClassCastException
    說明:ArrayList 的 subList 返回的是 ArrayList 的 內部類 SubList,只是 ArrayList 的一個視圖,對於 SubList 子列表的所有操作最終會反映到原列表。
    參考:
    ArrayList a = new ArrayList();
    for (int i = 0; i < 10; i++) {
        a.add()
    }
    List a2 = a.subList(0,5);
    System.out.println(a2); // [0, 1, 2, 3, 4]
    a2.add(10);
    System.out.println(a2); // [0, 1, 2, 3, 4, 10]
    System.out.println(a); // [0, 1, 2, 3, 4, 10, 5, 6, 7, 8, 9]
    
  5. 【強制】在 subList 的場景中,高度注意對原集合元素的增加或刪除,均會導致子列表的遍歷、增加、刪除產生 ConcurrentModificationException 異常。
  6. 【強制】使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入類型完全一樣的數組,大小就是 list.size()。
    說明:當使用 toArray 帶參方法,入參分配的數組空間不夠大時,toArray 方法內部將重新分配內存空間,並返回新數組地址;如果數組元素大於實際所需,下標爲 [list.size()] 的數組元素將被置爲 null ,其他數組元素保持原值,因此最好將方法入參數組大小定義爲與集合數組個數一致。
    正例:
    List<String> list = new ArrayList<>();
    list.add("guan");
    list.add("bao");
    String[] array = new String[list.size()];
    array = list.toArray(array);
    // array = (String[]) list.toArray();
    System.out.println(array);
    for (int i = 0; i < array.length; i++) {
        System.out.println(array[i]);
    }
    反例:直接使用 toArray 無參方法存在問題。此方法返回值只能是 Object[] 類,若強轉爲其他類型,數組將出現 ClassCastException 錯誤。
  7. 【強制】在使用 Collection 接口任何實現類的 addAll() 方法時,都要對輸入的集合參數進行 NPE 判斷。
    說明:在 ArrayList # addAll 方法的第一行代碼即 Object[] a = c.toArray();其中 c 爲輸入集合參數,如果爲 null ,則直接拋出異常。
  8. 【強制】Arrays.asList()  把數組轉換成集合時,不能使用其修改集合相關的方法,可查看上面第三條。
    說明:asList 體現的是適配器模式,只是轉換接口,後臺的數據仍是數組。修改原數組,list 會隨之修改。
    第一種情況:list.add("yangguanbao") 運行時異常。
    第二種情況:如果 str[0] = "changed";,list.get(0) 也會隨之修改,反之毅然。
  9. 【強制】泛型通配符 <? extends T> 用來接收返回的數據,此寫法的泛型集合不能使用 add 方法,而 <? super T> 不能使用 get 方法,因爲作爲接口調用賦值時易出錯。
    說明:擴展說一下 PECS(Producer Extends Consumer Super) 原則第一,頻繁往外讀取內容的,適合用 <? extends T>;第二,經常往裏插入的,適合用 <? super T>
  10. 【強制】在無泛型限制定義的集合賦值泛型限制集合時,當使用集合元素時,需要進行 instanceof 判斷,避免拋出 ClassCastException 異常。
    說明:畢竟泛型是在 JDK5 之後纔出現的,考慮到向前兼容,編譯器允許非泛型集合與泛型集合互相賦值。
    反例:
            // 泛型限制
            List<String> generics = null;
            // 無泛型限制
            List notGenerics = new ArrayList();
            notGenerics.add("str");
            notGenerics.add(1);
            generics = notGenerics;
            Object in;
            for (int i = 0; i < generics.size(); i++) {
                in = generics.get(i);
                if (!(in instanceof String)) {
                    in = in.toString();
                }
                System.out.println(in);
            }
            // 直接使用,由於不是 String 類型,報 ClassCastException
            System.out.println(generics.get(1));
  11. 【強制】不要在 foreach 循環裏進行元素的 remove / add 操作。remove 元素使用 Iterator 方式,如果併發操作,需要對 Iterator  對象加鎖。
    正例:
    反例:ConcurrentModificationException
            List<Integer> forList = new ArrayList();
            for (int i = 0; i < 10; i++) {
                forList.add(i);
            }
            // 正例
            Iterator<Integer> integerIterator = forList.iterator();
            while (integerIterator.hasNext()) {
                Integer item =integerIterator.next();
                if (item > 4){
                    integerIterator.remove();
                }
            }
            System.out.println("forList:");
            for (int i = 0; i < forList.size(); i++) {
                System.out.print("\t" + forList.get(i));
            }
            // 反例:刪除一個後,異常
            try {
                // 對列表循環訪問時異常 ConcurrentModificationException
                for (Integer i : forList) {
                    if (i < 5) {
                        forList.remove(i);
                    }
                }
            }catch (Exception e){
                System.out.println();
                e.printStackTrace();
            }
            System.out.println("forList:");
            for (int i = 0; i < forList.size(); i++) {
                System.out.print("\t" + forList.get(i));
            }
  12. 【強制】在JDK 7 及以上版本中,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<String>(){
                @Override
                public int compare(String o1, String o2) {
                    return o1.length() > o2.length() ? 1 : -1;
                }
            };
  13. 【推薦】在集合初始化時,指定集合初始值大小,避免不斷髮生擴容。
    說明:HashMap 使用 HashMap(int initialCapacity) 初始化。
    正例:initialCapacity =(需要存儲的元素個數 / 負載因子)+ 1。注意負載因子(loader factor)默認爲 0.75,如果暫時無法確定初始值大小,請設置爲16(默認值)。
    反例:HashMap 需要放置 1024 個元素,由於沒有設置容量初始大小,隨着元素不斷增加,容量被迫擴大 7 次,resize 需要重建 hash 表,這嚴重影響性能。
  14. 【推薦】使用 entrySet 遍歷 Map 類集合K / V,而不是用 keySet方式遍歷。
    說明:keySet 其實遍歷了兩次,一次是轉爲 Iterator 對象,另一次是從 hashMap 中取出 key 所對應的 value。如果是 JDK8,使用Map.forEach 方法。
    正例:values() 返回的是 V 值集合,是一個 list 集合對象;
               keySet() 返回的是 K 值集合,是一個 set 集合對象;
               entrySet() 返回的是 K / V 值組合集合。
    Map<String,Object> map = new HashMap<>(16);
            map.put("k1","value1");
            map.put("k2","value2");
            map.put("k3","value3");
            map.put("k4","value4");
            map.forEach(new BiConsumer<String, Object>() { // lambda (key, value) -> { System.out.println("k = " + key + " , " + "v = " + value); }
                @Override
                public void accept(String s, Object o) {
                    System.out.println("k = " + s + " , " + "v = " + o);
                }
            });
    
            for (Map.Entry entry : map.entrySet()) {
                System.out.println("k = " + entry.getKey() + " , " + "v = " + entry.getValue());
            }
  15. 【推薦】高度注意 Map 類集合 K / V 能不能存儲 null 值的情況:
    集合類 Key Value Super 說明
    Hashtable 不允許爲null 不允許爲null Dictionary 線程安全
    ConcurrentHashMap 不允許爲null 不允許爲null AbstracMap 鎖分段技術(JDK8:CAS)
    TreeMap 不允許爲null 允許爲null AbstractMap 線程不安全
    HashMap 允許爲null 允許爲null AbstractMap

    線程不安全

  16. 【參考】合理利用集合的有序性(sorted)和穩定性(ordered),避免集合的無序性(unsorted)和不穩定性(unordered)帶來的負面影響。
    說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性是指集合每次遍歷的元素次序是一定的。如:ArrayList 是  order / unsort;HashMap 是 unorder / unsort;TreeSet 是 order / sort 。
  17. 【參考】利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List 的 container 方法進行遍歷、對比、去重操作。

併發處理

  1. 【強制】獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。
    說明:資源驅動類,工具類,單例工廠類都需要注意。
  2. 【強制】在創建線程或線程池時,請指定有意義的線程名稱,方便出錯時回溯。
    正例:自定義線程工廠根據外部特徵進行分組,比如來自同一機房的調用,把機房編號賦值給whatFeatureOfGroup。
    public class UserThreadFactory implements ThreadFactory {
    
        private final String namePrefix;
    
        private final AtomicInteger nextId = new AtomicInteger(1);
    
        // 定義線程組名稱,使用 jstack 排查線程問題時,非常有幫助
        public UserThreadFactory(String whatFeatureOfGroup) {
            namePrefix = "From User ThreadFactory's " + whatFeatureOfGroup + "-Worker-";
        }
    
        @Override
        public Thread newThread(Runnable task) {
            String name = namePrefix + nextId.getAndIncrement();
            Thread thread = new Thread(null,task,name,0);
            System.out.println(thread.getName());
            return thread;
        }
    }
  3. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯示創建線程。
    說明:使用線程池的好處是減少在創建和銷燬線程上所消耗的時間及系統資源,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。

  4. 【強制】線程池不允許使用 Executors 創建,而是通過 ThreadPoolExecutor 的方式創建,這樣的處理方式能讓編寫代碼的工程師更加明確線程池的運行規則,規避資源耗盡的風險。
    說明:
    Executors 返回的線程池對象弊端如下。
    1)FixedThreadPool 和 SingleThreadPool :允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
    2)CachedThreadPool 允許創建的線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,導致OOM。

  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 代替 SimpleDateFormat,官方解釋:simple beautiful,strong,immutable,thread-safe。
    參考:Instant、LocalDateTime與DateTimeFormatter

  6. 【強制】必須回收自定義 ThreadLocal 變量,尤其在線程池場景下,線程經常會被複用,如果不清理自定義的 ThreadLocal 變量,可能會影響後續的業務邏輯和造成內存泄露等問題。儘量在代碼中使用 try-finally 塊進行回收。
    正例:

    class XXX {
        private static final ThreadLocal<Object> OBJECT_THREAD_LOCAL = new ThreadLocal<>();
        
        method { 
            OBJECT_THREAD_LOCAL.set("線程私有");
            try {
                System.out.println(OBJECT_THREAD_LOCAL.get());
            }finally {
                OBJECT_THREAD_LOCAL.remove();
            }
        }
    }
    
  7. 【強制】在高併發場景中,同步調用應該去考量鎖的性能消耗。能用無鎖結構,就不用鎖;能鎖住塊,就不鎖住整個方法體;能用對象鎖,就不要用類鎖
    說明:使加鎖的代碼塊工作量儘可能小,避免在鎖代碼塊中使用RPC方法。

  8. 【強制】在多個資源、數據庫表、對象同時加鎖,需要保持一致的加鎖順序,否則可能會造成死鎖。
    說明:如果線程一需要對錶A、B、C依次全部加鎖後纔可以進行更新操作,那麼線程二的加鎖順序也必須是A、B、C,否則可能出現死鎖。

  9. 【強制】在 try 代碼塊之前調用 Lock 實現類的 lock() 方法,避免由於加鎖失敗,導致 finally 調用 unlock() 出現異常。
    說明:在 lock() 中可能拋出 uncheck 異常,如果放在 try 代碼塊中,必然觸發 finally 中的 unlock 方法,它會調用 AQS 的 tryRelease 方法。對未加鎖的對象解鎖會拋出 unchecked 異常,如 IllegalMonitorStateException,雖然都是加鎖失敗造成的程序中斷,但是真正加鎖出錯信息可能被後者覆蓋。
    反例:

    Lock lock = new XxxLock();
    preDo();
    try{
        // 無論加鎖是否,都會執行解鎖操作
        lock.lock();
        do();
    }finally{
        lock.unlock();
    }
  10. 【強制】在併發修改同一記錄時,爲避免更新丟失,加鎖。要麼應用層加鎖,要麼在緩存層加鎖,要麼在數據庫使用樂觀鎖,使用 version 作爲更新標準。
    說明:如果每次訪問衝突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數 >= 3。

  11. 【強制】對於多線程處理定時任務的情況,在 Timer 運行多個 TImeTask 時,只要其中之一沒有捕獲拋出的異常,其他任務便會自動終止運行。如果在處理定時任務時使用 ScheduleExecutorService,則沒有這個問題。

  12. 【推薦】與資金相關的金融敏感信息,使用悲觀鎖策略。

  13. 【推薦】使用 CountDownLatch 進行異步轉同步操作,每個線程退出前必須調用 countDown 方法,線程執行代碼注意 catch 異常,確保 countDown 方法被執行,避免主線程無法執行至 await 方法,直到超時才返回結果。
    說明:子線程拋出異常堆棧,不能在主線程 try-catch到。

  14. 【推薦】避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed 導致性能下降。
    說明:Random 實例包括 java.util.Random 的實例或者 Math.random() 的方式。
    正例:在 JDK7之後,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要編碼保證每個線程有一個實例。

  15. 【推薦】在併發場景下,通過雙重檢查鎖(double-checked-locking)實現延遲初始化的優化問題隱患,推薦解決方案中較爲簡單的一種(適用於 JDK5 及以上版本),即目標屬性聲明爲 volatile。
    反例:

    class LazyLoadDemo {
        private Helper helper = null; //雙重檢查 並 聲明爲 volatile
        public Helper getHelper() {
            if (helper == null) synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
    }
  16. 【參考】volatile 解決多線程內存不可見問題。對於一寫多讀,可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。如果是 count++ 操作,使用如下類實現:

    AtomicInteger count = new AtomicInteger();
    count.addAndGet(1);

    JDK8 推薦使用 LongAdder 對象,它比 AtomicLong 性能更好(減少樂觀鎖的重試次數)。

  17. 【參考】HashMap 在容量不夠進行 resize 時,由於高併發可能出現死鏈,導致CPU 飆升,可以使用其他數據結構或加鎖來規避此風險。

  18. 【參考】ThreadLocal 對象使用 static 修飾。
    說明:這個變量針對一個線程內所有操作共享,所以設置爲靜態變量,所有此類的實例共享此靜態變量,在類第一次被使用時裝載,只分配一塊內存。所有此類的對象(只要是這個線程內定義的)都可以操控這個變量。

控制語句

  1. 【強制】當 switch 括號內的變量類型爲 String 並且此變量爲外部參數時,必須先進行 null 判斷。
  2. 【強制】在高併發場景中,避免使用“等於”判斷作爲中斷或退出的條件
    說明:如果併發控制沒有處理好,容易產生等值判斷被“擊穿” 的情況,應使用大於或小於的區間判斷條件來代替
    反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但因爲併發處理錯誤導致獎品數量瞬間變爲負數,這樣的話,活動無法終止。

註釋規約

  1. ...

其他

  1. 【強制】在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。
    說明:不要在方法體內定義:
    Pattern pattern = Pattern.compile(規則);
  2. 【強制】避免使用 Apache BeanUtils進行屬性 copy
    說明:Apache BeanUtils 性能較差,可以使用其他方案,比如 Spring BeanUtilsCglib BeanCopier,注意均是 淺拷貝。
  3. 【強制】不推薦使用 Math.random() 方法獲取隨機數,直接使用 Random 對象的 nextInt / nextLong .. 等方法。
  4. 【強制】獲取當前毫秒數用 System.currentTimeMills(),而不是 new Date().getTime()。
    說明:納秒:System.nanoTime();JDK8中,針對統計時間等場景,推薦使用 Instant類。
  5. 【強制】在對日期格式化時,傳入 pattern 中表示年份的統一用 小寫 y。
    說明:“yyyy” 表示當天所在年份,“YYYY” 表示本週(週日-週六)所在年份(JDK7之後),若本週跨年則返回的是下一年;注意:
                   “MM” - 月份;“mm” - 分鐘;“hh” - 12小時制;“HH” - 24小時制
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章