《Effective Java 》系列一

image.png

目錄

第二章 創(chuàng)建和銷毀對(duì)象
1 考慮用靜態(tài)工廠方法替代構(gòu)造器
2 遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)件器
3 用私有構(gòu)造器或者枚舉類型強(qiáng)化Singleton屬性
4 通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力
5 避免創(chuàng)建不必要的對(duì)象
6 消除過期的對(duì)象引用
7 避免使用終結(jié)方法

第三章 對(duì)于對(duì)象都通用的方法
8 Equals方法
9 HashCode方法
10 ToString方法
11 Clone方法
12 Comparable接口

第四章 類和接口 
13 使類和成員的可訪問性最小化
14 在公有類中使用訪問方法而非共有域
15 支持非可變性
16 復(fù)合優(yōu)先于繼承
17 要么專門為繼承而設(shè)計(jì),并給出文檔說明,要么禁止繼承
18 接口優(yōu)于抽象
19 接口只是被用來定義類型
20 類層次優(yōu)先于標(biāo)簽類
21 用函數(shù)對(duì)象表示策略
22 優(yōu)先考慮靜態(tài)成員類

第十章 并發(fā)
66 同步訪問共享的可變數(shù)據(jù)
67 避免過渡同步
68 executor和task優(yōu)先于線程
69 并發(fā)工具優(yōu)先于wait和notify
70 線程安全性的文檔化
71 甚用延遲初始化
72 不要依賴于線程調(diào)度器
73 避免使用線程組

第二章 創(chuàng)建和銷毀對(duì)象

1 考慮用靜態(tài)工廠方法替代構(gòu)造器

對(duì)于代碼來說, 清晰和簡(jiǎn)潔是最重要的.

代碼應(yīng)該被重用, 而不應(yīng)該被拷貝

模塊之間的依賴應(yīng)該盡可能的小.

錯(cuò)誤應(yīng)該盡早被檢測(cè)出來, 最好是在編譯時(shí)刻.

代碼的清晰, 正確, 可用, 健壯, 靈活和可維護(hù)性比性能更重要.

編程的藝術(shù)應(yīng)該是先學(xué)會(huì)基本規(guī)則, 然后才能知道在什么時(shí)候打破這些規(guī)則.

靜態(tài)工廠方法慣用的名稱:

  • valueOf
  • of
  • getInstance
  • newInstance
  • getType
  • newType

類可以提供一個(gè)公有的靜態(tài)工廠方法,他只是一個(gè)返回類的實(shí)例的靜態(tài)方法。

實(shí)例受控類

public static Boolean valueOf(boolean b)
{
    return b ? Boolean.TRUE : Boolean.FALSE;
}

編寫實(shí)例受控類有幾個(gè)原因。實(shí)例受控使得類可以確保他是一個(gè)Singleton或者是不可實(shí)例化的。他還使得不可變類可以確保不會(huì)存在兩個(gè)相等的實(shí)例。

API可以返回對(duì)象,同時(shí)又不會(huì)使對(duì)象的類變成公有的。以這種方式隱藏實(shí)現(xiàn)類會(huì)使API變得非常簡(jiǎn)介。這種結(jié)束適用于基于接口的框架(java.util.Collections)

這樣做有幾大優(yōu)勢(shì)。

  • 他們有名稱。
  • 不必再為每次調(diào)用他們都創(chuàng)建一個(gè)新對(duì)象。
  • 他們可以返回原返回類型的任何子類型的對(duì)象。
  • 在創(chuàng)建參數(shù)化類型實(shí)例的時(shí)候,他們是代碼變得更加簡(jiǎn)潔。

靜態(tài)工廠方法的缺點(diǎn)

  • 類如果不含公有的或者受保護(hù)地構(gòu)造器,就不能被子類化。
  • 他們與其他的靜態(tài)方法實(shí)際上沒有任何區(qū)別。

2 遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)件器

靜態(tài)工廠和構(gòu)造器有個(gè)共同的局限性:他們都不能很好的擴(kuò)展大量的可選參數(shù)。

對(duì)于需要大量的可選參數(shù)的時(shí)候,一向習(xí)慣采用重疊構(gòu)造器模式。

重疊構(gòu)造器模式可行,但是當(dāng)有許多參數(shù)的時(shí)候,客戶端代碼會(huì)很難編寫,并且仍然較難以閱讀。
遇到許多構(gòu)造器參數(shù)的時(shí)候,還有第二種代替辦法,即JavaBeans模式。

JavaBeans模式自身有著很嚴(yán)重的缺點(diǎn)。因?yàn)闃?gòu)造過程被分到了幾個(gè)調(diào)用中,在構(gòu)造過程中JavaBean可能處于不一致的狀態(tài)。

JavaBeans模式阻止了把類變成不可變的可能,這就需要程序員付出額外的努力來確保他的線程安全。

還有第三種替代方法,既能保證向重疊構(gòu)造器模式那樣的安全性,也能保證像JavaBeans模式那么好的可讀性。這就是Builder模式。

不直接生成想要的對(duì)象,而是讓客戶端利用所有必要的參數(shù)調(diào)用構(gòu)造器,得到一個(gè)Builder對(duì)象。然后客戶端在builder對(duì)象上調(diào)用類似setter的方法,來設(shè)置每個(gè)相關(guān)的可選參數(shù)。

  public class NutritionFacts {
  private final int servingSize;
  private final int servings;
  private final int calories;
  private final int fat;
  private final int sodium;
  private final int carbohydrate;
 
  public static class Builder {
    private final int servingSize;
    private final int servings;
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;
 
    public Builder(int servingSize, int servings) {
         this.servingSize = servingSize;
         this.servings = servings;
    }
 
    public Builder calories(int val) {
         ccalories = val; return this;
    }
 
    public Builder fat(int val) {
         fat = val; return this;
    }
 
    public Builder carbohydrate(int val) {
         carbohydrate = val; return this;
    }
 
    public Builder sodium(int val) {
      sodium = val; return this;
    }
 
    public NutritionFacts build() {
         return new NutritionFacts(this);
    }
  }
 
  private NutritionFacts(Builder builder) {
    servingSize = builder.servingSize;
    servings = builder.servings;
       calories = builder.calories;
       fat = builder.fat;
       sodium = builder.sodium;
       carbohydrate = builder.carbohydrate;   
  }
 
  public static void main(String[] args) {
    NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).calories(100).build();
  }
}

Builder像個(gè)構(gòu)造器一樣,可以對(duì)其參數(shù)加強(qiáng)約束條件。Build方法可以檢驗(yàn)這些約束條件。

將參數(shù)從builder拷貝到對(duì)象中之后,并在對(duì)象域而不是在Builder域(39)中對(duì)他們進(jìn)行檢驗(yàn),如果違反了任何約束條件,build方法就應(yīng)該拋出IllegalStateException(60)。異常的詳細(xì)信息應(yīng)該顯示出違反了那些約束條件。

設(shè)置參數(shù)的builder生成了一個(gè)很好的抽象工廠。

public  interface Builder<T> {
  public T build();
}
NutritionFacts.Builder implements Builder<NutritionFacts>
 
Tree buildTree(Builder<? Extends Node> nodeBuilder) {}

Class.newInstance破壞了編譯時(shí)的異常檢查。而Builder接口彌補(bǔ)了這些不足。

如果類的構(gòu)造器或者靜態(tài)工廠中具有多個(gè)參數(shù),設(shè)計(jì)這種類時(shí),Builder模式就始終不錯(cuò)的選擇。

3 用私有構(gòu)造器或者枚舉類型強(qiáng)化Singleton屬性

 public class Elvis {
  public static final Elvis INSTANCE = new Elvis();
  private Elvis {}
}
 

或者:

 public class Elvis {
  private staic final Elvis INSTANCE = new Envis();
  private Elvis() {}
  private static Elvis getInstance() { return INSTANCE; }
}

工廠方法的優(yōu)勢(shì)之一在于,它提供了靈活性:在不改變其API的前提下,我們可以改變?cè)擃愂欠駪?yīng)該為Singleton的想法。

工廠方法返回該類的唯一實(shí)例,但是,他可以很容易的被修改,比如改成每個(gè)調(diào)用該方法的線程返回一個(gè)唯一的實(shí)例。第二個(gè)優(yōu)勢(shì)與泛型有關(guān)(27)。

4 通過私有構(gòu)造器強(qiáng)化不可實(shí)例化的能力

企圖通過將類做成抽象類來強(qiáng)制該類不可被實(shí)例化,這是行不通的。該類可以被子類化,并且該子類也可以被實(shí)例化。

這要讓這個(gè)類包含私有構(gòu)造器,他就不能被子類化了:

public class UtilityClass {
  private UtilityClass() {
    throw new AssertionError();
  }
} 
 

AssertionError不是必需的,但是它可以避免不小心在類的內(nèi)部調(diào)用構(gòu)造器。他保證該類在任何情況下都不會(huì)被實(shí)例化。

5 避免創(chuàng)建不必要的對(duì)象

對(duì)于同時(shí)提供了靜態(tài)工廠方法(1)和構(gòu)造器的不可變類,通常可以使用靜態(tài)工廠方法而不是構(gòu)造器,以避免不必要的對(duì)象。

例如,靜態(tài)工廠方法Boolean.valueOf(String)幾乎總是優(yōu)先與構(gòu)造器Boolean(String)。構(gòu)造器在每次被調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)新的對(duì)象,而靜態(tài)工廠方法則從來不要求這樣做,實(shí)際上也不會(huì)這樣做。

 public class Person {
  private final Date birthDate;
  public boolean isBabyBoomer() {
    Calendar gmtCal =Calendar.getInstance(TimeZone.getTimeZOne(“GMT”));
    gmtCal.set(1946,Calendar.JANUARY, 1, 0, 0, 0);
       Date boomStart = gmtCal.getTime();
 
    gmtCal.set(1965, , 1, 0, 0, 0);
    Date boomEnd = gmtCal.getTime();
 
       return brithDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
  }
}

下面使用靜態(tài)的初始化器:

 public class Person {
  private final Date brithDate;
  private static final Date BOOM_START;
  private static final Date BOOM_END;
  static {
     Calendar gmtCal =Calendar.getInstance(TimeZone.getTimeZone(“GMT”));
     gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);   
        BOOM_START = gmtCal.getTime();    
        gmtCaL.set(1965, Calendar.JANUARY, 1, 0, 0, 0);    
        BOOM_END = gmtCal.getTime();  
    }     
    public boolean isBabyBoomer() {     
        return brithDate.compareTo(BOOM_START) >= 0 && brithDate.compareTo(BOOM_END) < 0;  
    }
}
 

如果改進(jìn)后的person類被初始化了,他的isBadyBoomer方法卻永遠(yuǎn)不會(huì)被調(diào)用,那就沒有必要初始化BOOM_START和BOOM_END域。

通過延遲初始化(71),即把對(duì)這個(gè)域的初始化延遲到isBadyBoomer方法第一次被調(diào)用的時(shí)候進(jìn)行,則有可能消除這些不必要的初始化工作,但是不建議這樣做。
適配器是指這樣一個(gè)對(duì)象:把它功能委托給一個(gè)后備對(duì)象,從而為后備對(duì)象提供一個(gè)可以替代的接口。由于適配器除了后備對(duì)象之外,沒有其他的狀態(tài)信息,所以針對(duì)給定對(duì)象的特定適配器而言,他不需要?jiǎng)?chuàng)建多個(gè)適配器實(shí)例。(Map接口的keySet)

不要錯(cuò)誤的認(rèn)為本條目所介紹的內(nèi)容暗示著“創(chuàng)建對(duì)象代價(jià)非常昂貴,我們應(yīng)該要盡可能的避免創(chuàng)建對(duì)象”。相反,由于小對(duì)象的構(gòu)造器制作很少量的顯式工作,所以,小對(duì)象的創(chuàng)建和回收動(dòng)作是非常廉價(jià)的,特別是在現(xiàn)代的JVM實(shí)現(xiàn)上更是如此。

反之,通過維護(hù)自己的對(duì)象池來避免創(chuàng)建對(duì)象并不是一種好的做法,除非池中的對(duì)象時(shí)非常重量級(jí)的。(數(shù)據(jù)庫連接池)

6 消除過期的對(duì)象引用

public class Stack {
  privateObject[] elements;
  private int size;
  private static final int DEFAULT_INITIAL_CAPACITY =16;
 
  public Stack() {
    elements = newObject[DEFAULT_INITIAL_CAPACITy];
  }
 
  public void push() {
    ensureCapacity();
    elements[size++] = e;
  }
 
  public Object pop() {
    if (size == 0)
      throw new EmptyStackException();
    returnn elements[--size];
  }
  private void ensureCapacity() {
    if (elements.length == size)
      elements = Arrays.copyOf(elements, size >> 1);
  }
} 

如果一個(gè)棧先是增長,然后再收縮,那么從棧中彈出來的對(duì)象將不會(huì)被當(dāng)做垃圾回收,即使使用棧的程序不在引用這些對(duì)象,他們也不會(huì)被回收。這是因?yàn)椋瑮5膬?nèi)部維護(hù)著對(duì)這些對(duì)象的過期引用。所謂過期引用,是指永遠(yuǎn)也不會(huì)被解除的引用。

修復(fù)辦法:

 publicObject pop() {
  if (size == 0)
    throw new EmptyStackException();
  Object result = elements[--size];
  elements[size] = null;
  return result;
}

清空過期引用的另一個(gè)好處是,如果他們以后又被錯(cuò)誤地解除引用,程序就會(huì)立即拋出NullPointerException異常,而不是悄悄地錯(cuò)誤運(yùn)行下去。

清空對(duì)象引用應(yīng)該是一種例外,而不是一種規(guī)范行為。

  • 只要類時(shí)自己管理內(nèi)存,程序員就應(yīng)該警惕內(nèi)存泄漏問題。
  • 內(nèi)存泄漏的另一個(gè)常見來源是緩存。(WeakHashMap)。
  • 內(nèi)存泄漏的第三個(gè)常見來源是監(jiān)聽器和其他回調(diào)。

7 避免使用終結(jié)方法

終結(jié)方法通常是不可預(yù)測(cè)的,也是很危險(xiǎn)的,一般情況下是不必要的。

Java語言規(guī)范不僅不保證終結(jié)方法會(huì)被及時(shí)的執(zhí)行,而且根本就不保證他們會(huì)被執(zhí)行,當(dāng)一個(gè)程序終止的時(shí)候,默寫已經(jīng)無法訪問的對(duì)象上的終結(jié)方法卻根本沒有被執(zhí)行,這是完全有可能的。

不應(yīng)該以來中介方法來更新重要的持久狀態(tài)。例如依賴和總結(jié)方法來釋放共享資源上的永久鎖,很容易讓整個(gè)分布式系統(tǒng)垮掉。

使用中介方法有一個(gè)非常嚴(yán)重的性能損失。

現(xiàn)實(shí)的種植方法通常與try-fainally結(jié)構(gòu)結(jié)合起來使用,以確保及時(shí)終止。

 Foo foo = new Foo();
try {
  // ……
} finally {
  foo.terminate();
}

本地對(duì)等體是個(gè)一個(gè)本地對(duì)象,普通對(duì)象通過本地方法委托給一個(gè)本地對(duì)象。因?yàn)楸镜貙?duì)等體不是一個(gè)普通的對(duì)象,所以垃圾回收期不知道他,當(dāng)他的Java對(duì)等體被回收的時(shí)候,他不會(huì)被回收。終止方法可以是本地方法,或者他也可以調(diào)用本地方法。
那么終結(jié)方法的好處:

  • 當(dāng)對(duì)象的所有者忘記調(diào)用前面段落中建議的顯式終止方法時(shí),終止方法可以充當(dāng)安全網(wǎng)。
  • 在本地方法體并不擁有關(guān)鍵資源的前提下,終結(jié)方法正式執(zhí)行回收任務(wù)的最合適的工具。

終結(jié)方法鏈:

try {
  // ……
} finally {
  super.finalize();
} 

如果終結(jié)方法發(fā)現(xiàn)資源還未被終止,啫應(yīng)該在日志中記錄一條警告,因?yàn)檫@表示客戶端中的一個(gè)Bug,應(yīng)該被修復(fù)。

(FileInputStream、FileOutputStream、Timer、Connection),都具有終結(jié)方法,這些終結(jié)方法充當(dāng)了安全網(wǎng)。
如果子類實(shí)現(xiàn)了超類的終結(jié)方法,但是忘了手工調(diào)用超類的終結(jié)方法,防范方法是為每個(gè)被終結(jié)的對(duì)象創(chuàng)建一個(gè)附加對(duì)象。

把終結(jié)方法放在一個(gè)匿名類中,該匿名類唯一的用途就是終結(jié)他的外圍實(shí)例。該匿名類的單個(gè)實(shí)力被稱為終結(jié)方法守衛(wèi)者。

 public class Foo {
  private final Object finalizerGuardian = newObject() {
    protected void finalize() throw Throwable {
      // Finalize outer Foo object
    }
  }
}

外圍實(shí)例在他的私有實(shí)例域存放著一個(gè)對(duì)其終結(jié)方法守衛(wèi)者的唯一引用,因?yàn)榻K結(jié)方法守衛(wèi)與外圍實(shí)例可以同時(shí)啟動(dòng)終結(jié)過程。當(dāng)守衛(wèi)被終結(jié)的時(shí)候,他執(zhí)行外圍實(shí)例所期望的終結(jié)行為,就好像他的終結(jié)方法是外圍對(duì)象上的一個(gè)方法一樣。

第三章 對(duì)于對(duì)象都通用的方法

8 Equals方法

重寫equals方法規(guī)范

  • 自反性
  • 對(duì)稱性
  • 傳遞性
  • 一致性:對(duì)于任意的應(yīng)用值x和y,如果對(duì)象信息沒有修改,那么多次調(diào)用總是返回true,或false

9 HashCode方法

修改equals總是要修改hashCode

如果兩個(gè)對(duì)象根據(jù)equals方法返回是相等的,那么調(diào)用這兩個(gè)對(duì)象任一個(gè)對(duì)象的hashCode方法必須產(chǎn)生相同的結(jié)果

為不相等的對(duì)象產(chǎn)生不同的散列碼
boolean類型 v ? 0 : 1
byte, char, short類型 (int) v
long類型 (int) (v ^ (v >>> 32))
float類型 Float.floatToIntBits(v)
double類型 Double.doubleToLongBits(v)
Object類型 v == null ? 0 : v.hashCode()
array類型 遞歸調(diào)用上述方法

result = 37 * result + n;

10 ToString方法

總是改寫toString()方法

11 Clone方法

Cloneable接口
改變超類中一個(gè)受保護(hù)的方法的行為

Object的clone方法返回該對(duì)象的逐域拷貝,否則拋出一個(gè)
CloneNotSupportedException異常

x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)

拷貝一個(gè)對(duì)象往往會(huì)導(dǎo)至創(chuàng)建該類的一個(gè)新實(shí)例,
但同時(shí)他也會(huì)要求拷貝內(nèi)部的數(shù)據(jù)結(jié)構(gòu),這個(gè)過程中沒有調(diào)用構(gòu)造函數(shù)

cone方法是另一個(gè)構(gòu)造函數(shù),必須確保他不會(huì)傷害到原始的對(duì)象,
并且正確地建立起被克隆對(duì)象中的約束關(guān)系

clone結(jié)構(gòu)與指向可變對(duì)象的final域的正常用法是不兼容的

另一個(gè)實(shí)現(xiàn)對(duì)象拷貝的好辦法是提供一個(gè)拷貝構(gòu)造函數(shù)
public Yum(Yum yum)

靜態(tài)工廠

public static Yum newInstance(Yum yum)

12 Comparable接口

一個(gè)類實(shí)現(xiàn)了Comparable接口就表明他的實(shí)例具有內(nèi)在的排序關(guān)系

如果想為實(shí)現(xiàn)了Comparable接口的類增加一個(gè)關(guān)鍵特性,請(qǐng)不要
擴(kuò)展這個(gè)類,而是編寫一個(gè)不相關(guān)的類,其中包含一個(gè)域,其類型是的一個(gè)類,然后提供一個(gè)“視圖”方法返回這個(gè)域。

BigDecimal("1.0")
BigDecimal("1.00")

加入到HashMap中,HashMap包含2個(gè)元素,通過equals方法比較是不相等的
加入到TreeMap中,TreeMap包含1個(gè)元素,通過compareTo方法比較是相等的

第四章 類和接口

13 使類和成員的可訪問性最小化

要區(qū)別設(shè)計(jì)良好的模塊與設(shè)計(jì)不好的模塊,最重要的因素在于,這個(gè)模塊對(duì)于外部的其他模塊而言,是否隱藏其內(nèi)部數(shù)據(jù)和其他實(shí)現(xiàn)細(xì)節(jié)。

設(shè)計(jì)良好的模塊會(huì)隱藏所有的實(shí)現(xiàn)細(xì)節(jié),把他的API與他的實(shí)現(xiàn)清晰的隔離開來。然后,模塊之間之通過他們的API進(jìn)行通信,一個(gè)模塊不需要知道其他模塊的內(nèi)部工作情況。這概念被稱為信息隱藏或者封裝,是軟件設(shè)計(jì)的基本原則之一。

他可以有效地解除組成系統(tǒng)的模塊各模塊之間的耦合關(guān)系,使得這些模塊可以獨(dú)立地愾發(fā)、測(cè)試、優(yōu)化、使用、理解和修改。

Java程序設(shè)計(jì)語言提供了許多機(jī)制來協(xié)助信息隱藏。訪問控制機(jī)制決定了類、接口和成員可訪問性。

盡可能地使每個(gè)類或者成員不被外界訪問。
如果一個(gè)報(bào)己私有的頂層類,只是在某一個(gè)類的內(nèi)部被用到,就應(yīng)該考慮使他成為唯一使用他的那個(gè)類的私有嵌套類(22)。

實(shí)例域決不能是公有的。包含公有可變域的類并不是線程安全的。
長度為非零的數(shù)組總是可變的。類具有公有的靜態(tài)final數(shù)組域,或者返回這種域的訪問方法,這幾乎總是錯(cuò)誤的。

public static final Thing[] VALUES = { ... };

修正這個(gè)問題有兩種方法??梢允枪灿袛?shù)組變成私有的,并增加一個(gè)公有的不可變列表

 private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

另一種方法是,可以是數(shù)組變成私有的,并添加一個(gè)公有方法,他返回私有數(shù)組的一個(gè)備份。

private static final Thing[] PRIVATE_VALUE = { ... };
public static final Thing[] values() {
  return PRIVATE_VALUE.clone();
}

要在這兩種方法之間作出選擇,得考慮客戶端可能怎么處理這個(gè)結(jié)果。

出了共有靜態(tài)final域的特殊情形之外,公有類都不應(yīng)該包含共有域。并且要確保共有靜態(tài)final域所引用的對(duì)象都是不可變的。

14 在公有類中使用訪問方法而非共有域

如果類可以在他所在的包的外部進(jìn)行訪問,就提供訪問方法,以保留將來改變?cè)擃惖膬?nèi)部表示法的靈活性。

如果類是包級(jí)私有的,或者是私有的嵌套類,直接暴露他的數(shù)據(jù)域并沒有本質(zhì)的錯(cuò)誤。假設(shè)這些數(shù)據(jù)域確實(shí)描述了該類所提供的抽象。這種方法比訪問方法的做法更不會(huì)產(chǎn)生是覺混亂,無論是在類定義中,還是在使用該類的客戶端代碼中。

15 支持非可變性

非可變對(duì)象本質(zhì)上是線程安全的,他們不要求同步,并且可以自由共享。

  • 不要提供任何會(huì)修改對(duì)象的方法
  • 保證沒有可被子類改寫的方法
  • 是所有的域都是final的
  • 訴有的于都成為私有的
  • 保證對(duì)于任何可變組件的互斥訪問

對(duì)于頻繁用的到的值,為他們提供公有的靜態(tài)final常量:

public static final Complex ZORE = new Complex(0, 0);
public static final Complex One = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);

這種方可以被進(jìn)一步擴(kuò)展。不可變的類可以提供一些靜態(tài)工廠,他們把頻繁被請(qǐng)求的實(shí)例緩存起來,從而當(dāng)現(xiàn)有實(shí)例可以符合請(qǐng)求的時(shí)候,就不必創(chuàng)建新的實(shí)例。
不可變類真正唯一的缺點(diǎn)是,對(duì)于每個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象。

對(duì)于這種問題有兩種辦法:

  • 先猜測(cè)一下會(huì)經(jīng)常用到哪些多步驟的操作,然后將他們作為基本類型提供。如果模個(gè)多步驟操作已經(jīng)作為基本類型提供,不可變的類就可以不必在每個(gè)步驟單獨(dú)創(chuàng)建一個(gè)對(duì)象。(例如BigInteger有一個(gè)包級(jí)私有的可變配套類)
  • 如果無法預(yù)測(cè),最好的辦法是提供一個(gè)公有的可變配套類。(String類的配套類StringBuilder)。

不僅可以共享非可變對(duì)象,甚至也可以共享它們的內(nèi)部信息
BigInteger中的negate方法
非可變對(duì)象為其他對(duì)象提供了大量構(gòu)件
非可變類的缺點(diǎn)是,對(duì)于每一個(gè)不同的值都要求一個(gè)單獨(dú)的對(duì)象

使一個(gè)類成為final的兩種辦法

  • 讓這個(gè)類的沒一個(gè)方法都成為final的,而不是讓整個(gè)類都成為final的。(可擴(kuò)展)
  • 使其所有的構(gòu)造方法成為私有的,或者包級(jí)私有的,并增加送有靜態(tài)工廠來替代個(gè)構(gòu)造函數(shù)。(靜態(tài)工廠)
public static Complex valueOf(a, b) { return new Complex(a, b); }

擴(kuò)展則增加靜態(tài)方法 static Complex valueOfPolar(a, b) ...
如果當(dāng)前正在編寫的類,他的安全性依賴于BigInteger的非可變性,那么你必須檢查
一確定這個(gè)參數(shù)是不是一個(gè)真正的BigInteger,而不是一個(gè)不可新的子類實(shí)例。

if (arg.getClass() != BigInteger.class) r = new BigInteger(arg.toByteArray());

規(guī)則指出,沒有方法會(huì)修改對(duì)象,并且所有的域必須是final的。

除非有很好的理由要讓一個(gè)類成為可變類,否則就應(yīng)該是非可變的。

實(shí)際上這些規(guī)則比真正的要求強(qiáng)了一點(diǎn),為了提高性能實(shí)際上應(yīng)該是沒有一個(gè)方法能夠?qū)?duì)象的狀態(tài)產(chǎn)生外部可見的改變,然而許多非可變類擁有一個(gè)或者多個(gè)非final的冗余域,它們比一些開銷昂貴的計(jì)算結(jié)果緩存到這些域中,如果將來再次請(qǐng)求這些計(jì)算,則直接返回這些被緩存的值,從而節(jié)約了從新計(jì)算所需的開銷。這總技巧可以很好的工作應(yīng)為對(duì)象是非可變的,他的非可變行保證了這些計(jì)算如果被再次執(zhí)行的話,會(huì)產(chǎn)生相同的結(jié)果(延遲初始化,String的hashCode)
如果一個(gè)類不能被做成非可變類,那么你仍然應(yīng)該盡可能地限制它的可變性。
構(gòu)造函數(shù)應(yīng)該創(chuàng)建完全初始化的對(duì)象,所有的約束關(guān)系應(yīng)該在這時(shí)候建立起來。

16 復(fù)合優(yōu)先于繼承

與方法調(diào)用不同的是,繼承打破了封裝性。

上面的問題都來源于對(duì)方法的改寫動(dòng)作。如果你在擴(kuò)展一個(gè)類的時(shí)候,僅僅是增加新的方法,而不改寫已有的方法,你可能會(huì)認(rèn)為這樣做是安全的,但是也并不是完全沒有風(fēng)險(xiǎn)。

有一種辦法可以避免前面提到的所有問題,你不再是擴(kuò)展一個(gè)已有的類,而是在新的類中增加一個(gè)私有域,他引用了這個(gè)已有的類的一個(gè)實(shí)例。這種設(shè)計(jì)被稱作復(fù)合。

 public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;
 
    public InstrumentedSet(Set<E> s) {
        super(s);   
    }
     
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
 
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
 
    public int getAddCount() {
        return addCount;
    }
}
 
 
public class ForwardingSet<E> implements Set<E> {
    private final Set s;
    public ForwardingSet(Set<E> s) {
        this.s = s;   
    }
 
    public void add(E e) { return s.add(e); }
    // ......
}

應(yīng)為原有已有的類邊成了一個(gè)新類的一個(gè)組成部分。新類中的每個(gè)實(shí)例方法都可以被調(diào)用被包含的已有實(shí)例中對(duì)應(yīng)的方法,并返回它的結(jié)果。這被稱為轉(zhuǎn)發(fā),新類中的方法被稱為轉(zhuǎn)發(fā)方法。這樣的到的類會(huì)非常穩(wěn)固,他不依賴于已有類的事現(xiàn)細(xì)節(jié)。

每一個(gè)InstrumentedSet實(shí)例都把另一個(gè)Set實(shí)例包裝起來,所以InstrumentedSet類被稱作包裝類。(Decorutor模式)

包裝類不適合用在回調(diào)框架中,在回調(diào)框架中,對(duì)象把自己的引用傳遞給其他的對(duì)象,
已便將來調(diào)用回來,因?yàn)楸话b起來的對(duì)象并不知道他外面的包裝對(duì)象,所以他傳遞一個(gè)只向自己的引用,回調(diào)時(shí)繞開了外面的包裝對(duì)象這被稱為SELF問題。

只有當(dāng)子類真正是超類的子類型的時(shí)候,繼承才是合適的,對(duì)于正在擴(kuò)展的類,繼承機(jī)制會(huì)把超類API中的所有缺陷傳播到子類中,而復(fù)合技術(shù)運(yùn)允許你設(shè)計(jì)一個(gè)新的API從而隱藏這些缺陷。

17 要么專門為繼承而設(shè)計(jì),并給出文檔說明,要么禁止繼承

一個(gè)類必須通過某種形式提供適當(dāng)?shù)你^子,已便能夠進(jìn)入到它的內(nèi)部工作流程中,
這樣的形式可以是精心選擇的受保護(hù)的方法,也可以是保護(hù)域。

當(dāng)你為了繼承的目的而設(shè)計(jì)一個(gè)有可能被廣泛使用的類時(shí),比需要意識(shí)到,對(duì)于文檔中所說明的自用模式,以及對(duì)于其受保護(hù)方法和域所有隱含的實(shí)現(xiàn)細(xì)節(jié),你實(shí)際上已經(jīng)作出了永久的承諾。這些承諾使得你在后續(xù)的版本中要提高這個(gè)類的性能,或者增加新功能非常困難,甚至不可能。

構(gòu)造函數(shù)一定不能調(diào)用可被改寫的方法。超類的構(gòu)造函數(shù)將會(huì)在子類的構(gòu)造函數(shù)運(yùn)行之前先被調(diào)用,如果該改寫版本的方法依賴于子類構(gòu)造函數(shù)所執(zhí)行的初始化工作,那么該方法將不會(huì)如預(yù)期般的執(zhí)行。
無論是clone還是readObject,都不能他調(diào)用一個(gè)可改寫的方法,不管是直接地方是,還是間接地方式。

為了繼承而設(shè)計(jì)一個(gè)類,要求對(duì)這個(gè)類有一些實(shí)質(zhì)的限制。
對(duì)于這個(gè)問題的最好解決方案是,對(duì)于那些并非為了安全地進(jìn)行子類化而設(shè)計(jì)和編寫文檔的類,禁止子類化。

  • 1聲明為final類
  • 2把所有的構(gòu)造函數(shù)變成私有的,并增加一些公有靜態(tài)工廠來代替構(gòu)造函數(shù)

消除一個(gè)類中可改寫的方法而不改變它的行為,做法如下

  • 把每個(gè)可改寫的方法的代碼移到一個(gè)私有的輔助方法中,并讓每個(gè)可改寫的方法
  • 調(diào)用他的私有輔助方法。然后,用直接調(diào)用可改寫方法的私有輔助方法來代替可
  • 改寫方法的每個(gè)自用調(diào)用。

18 接口優(yōu)于抽象

已有的類可以很容易被更新,已實(shí)現(xiàn)新的接口。
接口使得我們可以構(gòu)造出非層次結(jié)構(gòu)的類型框架。

接口使得安全地增強(qiáng)一個(gè)類的功能成為可能。
可以把接口和抽象類的優(yōu)點(diǎn)結(jié)合起來,對(duì)于你期望導(dǎo)出的每一個(gè)重要接口,都提供一個(gè)抽想的骨架實(shí)現(xiàn)類。

實(shí)現(xiàn)了這個(gè)接口的類可以把對(duì)于接口方法的調(diào)用,轉(zhuǎn)發(fā)到一個(gè)內(nèi)部私有類的實(shí)例上,
而這個(gè)內(nèi)部私有類擴(kuò)展了骨架實(shí)現(xiàn)類。這項(xiàng)技術(shù)被稱為模擬多重繼承。

編寫一個(gè)骨架類相對(duì)比較簡(jiǎn)單,首先確定那些方法是最為基本的,其他的方法在實(shí)現(xiàn)的時(shí)候?qū)⒁运麄優(yōu)榛A(chǔ)。基本方法將是骨架實(shí)現(xiàn)類中的抽象方法,必須為接口中所有其他方法提供具體的實(shí)現(xiàn)。

抽象類的演化比接口的演化要容易得多。

19 接口只是被用來定義類型

一個(gè)類實(shí)現(xiàn)了一個(gè)接口,就表明客戶可以對(duì)這個(gè)類的實(shí)例實(shí)施某些動(dòng)作。
有一中接口被稱為常量接口,常量接口模式是對(duì)接口的不良使用。

要導(dǎo)出常量,可以有幾種選擇方案。如果這些常量被看作一個(gè)枚舉類型的成員,
那么你應(yīng)該應(yīng)用一個(gè)類型安全枚舉類(21),否則的話,你應(yīng)該使用一個(gè)不可被實(shí)例化的工具類。(3)

接口應(yīng)該使用來定義類型的,他們不應(yīng)該被用來導(dǎo)出常量。

20 類層次優(yōu)先于標(biāo)簽類

標(biāo)簽類過于冗長、容易出錯(cuò),并且效率低下。

 class Figure {
  enum Shape { RECTANGLE, CIRCLE };
 
  final Shape shape;
  double length;
  double width;
  double radius;
 
  Figure(double radius) {
    shape = Shape.CIRCLE;
    this.radius = radius;
  }
 
  Figure(double length, double width) {
    shape = Shape.RECTANGLE;
    this.length = length;
    this.width = width;
  }
 
  double area() { ... }
}
 

標(biāo)簽類正是類層次的一種簡(jiǎn)單的仿效。

 abstract class Figure {
  abstract double area();
}
 
class Circle extends Figure {
  final double radius;
 
  public Circle(double radius) {
    this.radius = raidus;
  }
 
  public area() { return Math.PI * radius; }
}
 
class Rectangle extends Figure {
  final double length;
  final double width;
 
  public Rectangle(double length, double width) {
    this.length = length;
    this.width = width;
  }
 
  double area() { return length * width; }
}

這段代碼簡(jiǎn)單且清楚,每個(gè)類型的實(shí)現(xiàn)都配有自己的類,這些類都沒有受到不相關(guān)的數(shù)據(jù)域的拖累。所有的域都是final的。

他們可以用來反應(yīng)類型之間本質(zhì)上的層次關(guān)系,有助于增強(qiáng)靈活性,并進(jìn)行更好的編譯時(shí)類型檢查。

21 用函數(shù)對(duì)象表示策略

有些語言支持函數(shù)指針、代理、lambda表達(dá)式,或者支持類似的機(jī)制,允許程序把“調(diào)用特殊函數(shù)的能力”存儲(chǔ)起來并傳遞這種能力。

Java沒有提供函數(shù)指針,但是可以用對(duì)象引用實(shí)現(xiàn)同樣的功能。調(diào)用對(duì)象上的方法通常是執(zhí)行該對(duì)象上的某項(xiàng)操作。

我們可以定義這樣一種對(duì)象,他的方法執(zhí)行其他對(duì)象上的操作。如果一個(gè)類僅僅導(dǎo)出這樣的一個(gè)方法,他的實(shí)例實(shí)際上就等同于一個(gè)指向該方法的指針。這樣的實(shí)例被稱為函數(shù)對(duì)象。

 class StringLengthComparator {
  public int compare(String s1, String s2) {
    return s1.length() - s2.length();
  }
}

函數(shù)指針可以載人一對(duì)字符串上被調(diào)用。換句話說,StringLengthComparator實(shí)例適用于字符串比較操作的具體策略。

作為典型的具體策略類,StringLengthComparator類是無狀態(tài)的:他沒有域,這個(gè)類的所有實(shí)例在功能上都是互相等價(jià)的。

我們?cè)谠O(shè)計(jì)具體的策略類時(shí),還需要定義一個(gè)策略接口:

 public interface Comparator<T> {
  public int compare(T t1, T t2);
}
 
Class StringLengthComparator implements Comparator<String > {}

具體策略類往往使用匿名類聲明。如果他被重復(fù)執(zhí)行,考慮將函數(shù)對(duì)象存儲(chǔ)到一個(gè)私有的靜態(tài)final域里,并重用他。

 Class Host {
  private static class StrLenCmp implements Comparator<String>, Serializable {
    public int compare(String s1, String s2) {
      return s1.length() - s2.length();
    }
  }
 
  public static final Comparator<String> STRING_LENGTH_COMPRATOR = new StrLenCmp();
}

宿主類可以到出公有的靜態(tài)域(或靜態(tài)工廠方法),起類型為策略接口,具體的策略類可以是宿主類的私有嵌套類。(String的不去分大小寫比較)

22 優(yōu)先考慮靜態(tài)成員類

嵌套類是指被定義在另一個(gè)類的內(nèi)部的類。
嵌套類存在的目的應(yīng)該只是為他的外圍類提供服務(wù)。
如果一個(gè)嵌套類將來有可能會(huì)用于其他的某個(gè)環(huán)境中,那么應(yīng)該是頂層類。

嵌套類有四種:靜態(tài)成員類,非靜態(tài)成員類,匿名類和局部類。

除了第一種其他三種都被稱為內(nèi)部類。

靜態(tài)成員類的一種通常用法是作為公有的輔助類,僅當(dāng)與他的外部類一起使用時(shí)才有意義。

非靜態(tài)成員類的每一個(gè)實(shí)例都隱含著與外圍類的外圍實(shí)例緊密關(guān)聯(lián)在一起。
在非靜態(tài)成員的實(shí)例方法內(nèi)部,調(diào)用外圍實(shí)例上的方法是有可能的,或者使用經(jīng)過修飾的this也可以得到一個(gè)指向外圍實(shí)例的引用。如果一個(gè)嵌套類的實(shí)例可以在他的外圍類的實(shí)例之外獨(dú)立存在,那么這個(gè)嵌套類不可能是一個(gè)非靜態(tài)成員類,在沒有外圍實(shí)例的情況下要?jiǎng)?chuàng)建非靜態(tài)成員類的實(shí)例是不可能的。

非靜態(tài)成員應(yīng)用Iteragor(迭代器)

如果你聲明的成員類不要求訪問外圍實(shí)例,那么請(qǐng)記住把static修飾符放到成員類的聲明中,使他成為一個(gè)靜態(tài)成員類,而不是一個(gè)非靜態(tài)成員類。

私有靜態(tài)成員類的一種通常方法是用來代表外圍類對(duì)象的組件。例如Map實(shí)例中的Entry每一對(duì)鍵值都與Map關(guān)聯(lián)但是Entry的方法并不需要訪問外圍類,如果是用非靜態(tài)成員來表示,每個(gè)Entry中將會(huì)包含一個(gè)指向Map的引用,只會(huì)浪費(fèi)空間。
匿名類沒有名字,所以他們被實(shí)例化之后就不能在對(duì)他們進(jìn)行引用,他不是外圍的一個(gè)成員,并不于其他的成員一起被聲明,而是在被使用的點(diǎn)上同時(shí)被聲明和實(shí)例化。匿名類可以出現(xiàn)在代碼中任何允許表達(dá)式出現(xiàn)的地方。匿名類的行為與靜態(tài)的或者非靜態(tài)的成員類非常類似,取決于他所在的環(huán)境:

如果匿名類出現(xiàn)在一個(gè)非靜態(tài)的環(huán)境中,則他有一個(gè)外圍實(shí)例。

常見用法:

  • 創(chuàng)建一個(gè)函數(shù)對(duì)象。比如Comparator
  • 創(chuàng)建一個(gè)過程對(duì)象。比如Thread
  • 在一個(gè)靜態(tài)工廠方法內(nèi)部。比如intArrayAsList(16條)
  • 在很復(fù)雜的類行安全枚舉類型中用于共有靜態(tài)final域的初始化器中(21條Operation類)
 public class Calculator
{
    public static abstract class Operation
    {
        private final String name;
 
        Operation(String name) { this.name = name; }
        public String toString() { return this.name; }
 
        abstract double eval(double x, double y);
 
        public static final Operation PLUS = new Operation("+") {
            double eval(double x, double y) { return x + y; }
        }
 
        public static final Operation MINUS = new Operation("-") {
            double eval(double x, double y) { return x - y; }
        }
 
        public static final Operation TIMES = new Operation("*") {
            double eval(double x, double y) { return x * y; }
        }
 
        public static final Operation DIVIDE = new Operation("/") {
            double eval(double x, double y) { return x / y; }
        }
    }
 
    public double calculate(double x, Operation op, double y) {
        return op.eval(x, y)   
    }
}

如果一個(gè)嵌套類需要在單個(gè)方法外仍然是可見的,或者太長了不適合放在一個(gè)方法內(nèi)部,那么應(yīng)該是用成員類。

如果成員類的每一個(gè)實(shí)例都需要一個(gè)只向起外圍實(shí)例的引用,則把成員類做成非靜態(tài)的;否則就做成靜態(tài)的。

第十章 并發(fā)

66 同步訪問共享的可變數(shù)據(jù)

對(duì)象在被創(chuàng)建的時(shí)候處于一直的狀態(tài),當(dāng)有方法訪問他的時(shí)候,他就被鎖住了。這些方法觀察到對(duì)象的狀態(tài),并且可能會(huì)引起狀態(tài)轉(zhuǎn)變,即把對(duì)象的一種一致狀態(tài)轉(zhuǎn)換到另一種一致的狀態(tài)。

while (!done) i++;

優(yōu)化后→

if (!done) while (true) i++;  

導(dǎo)致狀態(tài)的該并永遠(yuǎn)不會(huì)發(fā)生。這種優(yōu)化稱作提升,正是HotSpot Server VM的工作。結(jié)果是個(gè)活性失敗。

為了提高性能,在讀寫原子數(shù)據(jù)的時(shí)候,應(yīng)該避免使用同步。這個(gè)建議是非常危險(xiǎn)而錯(cuò)誤的。

雖然語言規(guī)范保證了線程在讀取原子數(shù)據(jù)的時(shí)候,不會(huì)看到任意的數(shù)值,但是他并不保證一個(gè)線程寫入的值對(duì)于另一個(gè)線程是可見的。為了在線程之間進(jìn)行可靠的通信,也為了互斥訪問,同步是必要的。

如果讀和寫操作沒有都被同步,同步就不會(huì)起作用。

private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
        return nextSerialNumber++;
}

問題在于,增量操作符(++)不是原子的。他在nextSerialNumber域中執(zhí)行兩項(xiàng)操作:

首先讀取值,然后寫回一個(gè)新值,相當(dāng)于原來的值再加上1

如果第二個(gè)線程在第一個(gè)線程讀取舊值和寫回新值期間讀取這個(gè)域

第二個(gè)線程就會(huì)與第一個(gè)線程一起看到同一個(gè)值,并返回相同的序列號(hào)。這就是安全性失敗。

修正generateSerialNumber方法的辦法是增加synchronized修飾符、Atomic類或者使用鎖。來確保調(diào)用不會(huì)交叉存取。

private static final AtomicLong nextSerialNum = new AtomicLong();
publib static long generateSerialNumber() {
    return nextSerialNum.getAndIncrement();
}

只同步共享對(duì)象引用的動(dòng)作。然后其他線程沒有進(jìn)一步的同步也可以讀取對(duì)象,只要他沒有再被修改。
這種對(duì)象被稱作事實(shí)上不可變的。將這種對(duì)象引用從一個(gè)線程傳遞到其他的線程被稱作安全發(fā)布。

安全發(fā)布對(duì)象引用有許多種方法:

  • 可以將它保存在靜態(tài)域中,作為類初始化的一部分;
  • 可以將它保存在volatile域、final域或者通過正常鎖定訪問的域中;或者可以將它放到并發(fā)的集合中。
  • 當(dāng)多個(gè)線程共享可變數(shù)據(jù)的時(shí)候,每個(gè)讀或者寫書據(jù)的線程都必須執(zhí)行同步。

67 避免過渡同步

為了避免活性失敗和安全性失敗,在一個(gè)被同步的方法或者代碼中,永遠(yuǎn)不要放棄對(duì)客戶端的控制。

在一個(gè)同步的區(qū)域內(nèi)部,不要調(diào)用設(shè)計(jì)成被覆蓋的方法,或者是由客戶端以函數(shù)對(duì)象的形式提供的方法(21)。

在同步區(qū)域外被調(diào)用的外來方法被稱作開放調(diào)用。除了可以避免死鎖之外,開放調(diào)用還可以極大的增加并發(fā)性。

通常,你應(yīng)該在同步區(qū)域內(nèi)作盡可能少的工作。獲得鎖,檢查共享數(shù)據(jù),根據(jù)需要轉(zhuǎn)換數(shù)據(jù),然后釋放鎖。如果你必須要執(zhí)行某個(gè)很耗時(shí)的動(dòng)作,則應(yīng)該設(shè)法把這個(gè)動(dòng)作移到同步區(qū)域外面,而不違背第66條中的指導(dǎo)方針。
如果你在內(nèi)部同步了類,就可以使用不同的方法來實(shí)現(xiàn)高并發(fā)性,例如分拆鎖、分離鎖和非阻塞并發(fā)控制。

如果方法修改了靜態(tài)域,那么你也必須同步對(duì)這個(gè)域的訪問,即使他往往之用于單個(gè)線程。

為了避免死鎖和數(shù)據(jù)破壞,千萬不要從同步區(qū)域內(nèi)部調(diào)用外來方法。

68 executor和task優(yōu)先于線程

ExecutorCompletionService

ScheduledThreadPoolExecutor

如果相讓不知一個(gè)線程來處理來自這個(gè)隊(duì)列的請(qǐng)求,只要調(diào)用一個(gè)不同的靜態(tài)工廠,這個(gè)工廠創(chuàng)建了一種不同的executor service,稱作線程池。

69 并發(fā)工具優(yōu)先于wait和notify

concurrent中更高級(jí)的工具分成三類:

  • Executor Framwork 執(zhí)行框架
  • Concurrent Collection 并發(fā)容器
  • Synchronizer 同步器

并發(fā)集合中不可能排除并發(fā)活動(dòng);將他所定沒有什么作用,只會(huì)使程序速度更慢。

同步器是一些使線程能夠等待另一個(gè)線程的對(duì)象,允許他們協(xié)調(diào)動(dòng)作。最長用的同步器是CountDownLatch和Semaphore,不常用的是CyclicBarrier和Exchanger。

 public static long time(Executor executor, int concurrency, final Runnable action) throws InterruptedException {
  final CountDownLatch ready = new CountDownLatch(concurrency);
  final CountDownLatch start new CountDownLatch(1);
  final CountDownLatch done = new CountDownLatch(concurrency);
 
  for (int i = 0; i < concurrency; i++) {
    executor.execute(new Runnable() {
      public void run() {
        ready.countDown();
        try {
          start.await();
          action.run();
        }
        catch (InterruptedException e) {
          Thread.currentThread().interrupt();
        }
        finally {
          done.countDown();
        }
      }
    });
  }
 
  ready.await();
  long startTime = System.nanoTime();
  start.countDown();
  done.await();
  return System.nanoTime() - startTime;
}

對(duì)于間歇式的定時(shí),始終應(yīng)該優(yōu)先使用System.nanoTime,而不是使用System.currentTimeMillis。System.nanoTime更加準(zhǔn)確也更加精確,他不受系統(tǒng)的實(shí)時(shí)時(shí)鐘調(diào)整所影響。
始終應(yīng)該使用wait循環(huán)模式來調(diào)用wait方法;永遠(yuǎn)不要在循環(huán)之外調(diào)用wait方法。循環(huán)會(huì)在等待之前和之后測(cè)試條件。

等待之前測(cè)試條件,當(dāng)條件已經(jīng)成立時(shí)就跳過等待,這對(duì)于確?;钚允潜匾摹H绻麠l件已經(jīng)成立,并且在線程等待之前,notify方法已被調(diào)用,則無法保證該線程從等待中蘇醒過來。

等待之后測(cè)試條件,如果條件不成立的話繼續(xù)等待,這對(duì)于確保安全性是必要的。當(dāng)條件不成立的時(shí)候,如果線程繼續(xù)執(zhí)行,則可能會(huì)破壞被鎖保護(hù)的約束關(guān)系。

當(dāng)條件不成立時(shí),下面一些理由可是一個(gè)線程蘇醒過來:

  • 另一個(gè)線程可能已經(jīng)的到了鎖,并且從一個(gè)線程調(diào)用notify那一刻起,到等待線程蘇醒過來的這段時(shí)間中,得到鎖的線程已經(jīng)改變了受保護(hù)的狀態(tài)。
  • 條件并不成立,但是另一個(gè)線程可能意外地或惡意的調(diào)用了notify。
    通知線程在喚醒等待線程時(shí)即使只有某些等待線程的條件已經(jīng)被滿足,當(dāng)時(shí)通知線程可能仍然調(diào)用notifyAll。
  • 在沒有通知的情況下,等待線程也可能會(huì)蘇醒過來。這稱為偽喚醒。

如果處于等待狀態(tài)的所有線程都在等待同一個(gè)條件,而每次只有一個(gè)線程可以從這個(gè)條件中被喚醒,那么你就應(yīng)該選擇調(diào)用notify,而不是notifyAll。

一般情況下,你應(yīng)該優(yōu)先使用notifyAll,而不是使用notify。

70 線程安全性的文檔化

在一個(gè)方法聲明中出現(xiàn)synchronized修飾符,這是個(gè)實(shí)現(xiàn)細(xì)節(jié),并不是導(dǎo)出的API的一部分。

一個(gè)類為了可悲多個(gè)線程安全地使用,必須在文檔中清楚地說明他所支持的線程安全級(jí)別。
包括:

  • 不可變的(String Long BigInteger)
  • 無條件的線程安全 (Random ConcurrentHashMap)
  • 有條件的線程安全 (Collections.synchronized)
  • 非線程安全 (ArrayList HashMap)
  • 線程對(duì)立的

有條件的線程安全必須指明:哪個(gè)方法條用序列需要外部同步,以及在執(zhí)行這些序列的時(shí)候要獲得哪把鎖。

應(yīng)對(duì)于Java并發(fā)編程實(shí)踐一書中的線程安全注解。在文檔中描述一個(gè)有條件的線程安全類要特別小心。

你必須致命那個(gè)調(diào)用序列需要外部同步,還要指明為了執(zhí)行這些序列,必須獲得哪一把所。

例外(一個(gè)對(duì)象代表了另一個(gè)對(duì)象的一個(gè)視圖,用戶通常就必須在后臺(tái)對(duì)象上同步,以防止其他線程直接修改后臺(tái)對(duì)象)

Map<K, V> map = Collections.synchronizedMap(new HashMap<K, V>);
 
Set<K> s = map.keySet();
synchronized (map) {
    for (K key : s) {
        k.method();
    }
} 

為了避免超時(shí)地保持公有可訪問鎖的攻擊,應(yīng)該使用一個(gè)私有鎖對(duì)象來代替同步的方法。

 private final Object lock = new Object();
 
public void foo() {
    synchronized(lock) {
        // ......
    }
}

lock域被聲明為final的。這樣可以防止不小心改變他的內(nèi)容,而導(dǎo)致不同步訪問對(duì)象的悲慘結(jié)果。

私有鎖對(duì)象模式只能用在無條件的線程安全類上。有條件的線程安全類不能使用這種模式。

私有鎖對(duì)象模式特別適用于那些專門為繼承而設(shè)計(jì)的類。

如果這種類使用他的實(shí)例作為鎖對(duì)象,子類可能很容易在無意種妨礙基類的操作,反之亦然。

71 甚用延遲初始化

延遲初始化是延遲到需要域的值時(shí)才能將它初始化的這種行為。

就像大多數(shù)的優(yōu)化一樣,對(duì)于延遲初始化,最好建議“除非絕對(duì)必要,否則就不要這么做”。

如果屬性只在類的實(shí)例部分被訪問,并且初始化這個(gè)屬性的開銷很高??赡芫椭档眠M(jìn)行延遲初始化。

當(dāng)有多個(gè)線程時(shí),延遲初始化是需要技巧的。如果多個(gè)線程共享一個(gè)延遲初始化的域,采用某種形式的同步是很重要的。

在大多數(shù)情況下,正常初始化要優(yōu)先于延遲初始化。

private final FieldType field = computeFieldValue();

如果利用延遲優(yōu)化來破壞初始化的循環(huán),就要使用同步訪問方法。

private FieldType field;
synchronized FieldType getField() {
    if (field == null) {
        field = computeFieldValue();
    }
    return field;
}

如果出于性能的考慮而需要對(duì)靜態(tài)域使用延遲初始化,就使用lazy initialization holder class模式。

 private static class FieldHolder {
    static final FieldType field = computeFieldValue();
}
 
static FieldType getField() {
    return FieldHolder.field;
}

當(dāng)個(gè)getField方法第一次被調(diào)用時(shí),他第一次讀取FieldHolder.field,導(dǎo)致FieldHolder類得到初始化。這種模式的魅力在于,getField方法沒有被同步,并且只能執(zhí)行一個(gè)域訪問,因此延遲初始化實(shí)際上并沒有增加任何訪問成本。
如果處于性能的考慮而需要對(duì)實(shí)例域使用延遲初始化,就使用雙重檢查模式。

這種模式避免了在域被初始化之后訪問這個(gè)域時(shí)的鎖定開銷。

思想是:兩次檢查域的值(雙重檢查),第一次檢查時(shí)沒有鎖定,看看這個(gè)域是否被初始化了;第二次檢查時(shí)有鎖定。當(dāng)只有第二次檢查時(shí)表明這個(gè)域沒有被初始化,才會(huì)調(diào)用computeFieldValue方法對(duì)這個(gè)域進(jìn)行初始化。

private volatile FieldType field;
 
FieldType getField() {
    FieldType result = field;
    if (result == null) {
        synchronized (this) {
            result = field;
            if (result == null) {
                field = result = computeFieldValue();
            }
        }
    }
    return result;
} 

對(duì)于需要用到局部變量result可能優(yōu)點(diǎn)不解。這個(gè)變量的作用是確保field只能在已經(jīng)被初始化的情況下讀取一次。
單重檢查模式:有時(shí)候你可能需要延遲初始化一個(gè)可以接受重復(fù)初始化的實(shí)例域。如果處于這種情況,就可以使用雙重檢查慣用法的一個(gè)變形,他省去了第二次檢查。

private
volatile FieldType field;
 
public FieldType getField() {
    FieldType result = field;
    if (result == null)
    field = result = computeFieldValue();
    return result;
}

當(dāng)雙重檢查模式或者單重檢查模式應(yīng)用到數(shù)值類型的基本類型域時(shí),就會(huì)用0來檢查這個(gè)域,而不是用null。
如果你不在意是否每個(gè)線程都重新計(jì)算域的值,并且域的類型為基本類型,而不是long或者double類型,就可以選擇從單重檢查模式的域聲明中刪除volatile修飾符。他加快了某些架構(gòu)上的域訪問,代價(jià)是增加了額外的初始化訪問該域的每一個(gè)線程都要進(jìn)行一次初始化。
對(duì)于實(shí)例域,就可以使用雙重檢查模式;對(duì)于靜態(tài)域,則可以使用惰性初始化;對(duì)于可以接受重復(fù)初始化的實(shí)例域,也可以考慮使用單重檢查模式。

72 不要依賴于線程調(diào)度器

任何依賴于線程調(diào)度器來達(dá)到正確性或者性能要求的程序,很有可能都是不可移植的。

要編寫健壯的、響應(yīng)良好的、可移植的多線程應(yīng)用程序,最好的辦法是確??蛇\(yùn)行線程的平均數(shù)量不明顯多于處理器的數(shù)量。

保持可運(yùn)行線程數(shù)盡可能少的主要方法是,讓每個(gè)線程做些有意義的工作,然后等待更多有意義的工作。如果線程沒有在做有意義工作,就不應(yīng)該運(yùn)行。

線程不應(yīng)該一直處于忙等得狀態(tài),即反復(fù)地檢查一個(gè)共享對(duì)象,以等待某些事情發(fā)生。

 public class SlowCountDownLatch {
    private int count;
 
    public SlowCountDownLatch(int count) {
        if (count < 0)
            throw new IllegalArgumentException();
        this.count = count;
    }
 
    public void await() {
        while (true) {
            synchronized (this) {
                if (count == 0) return;
            }
        }
    }
 
    public void countDown() {
        if (count != 0)
            count--;
    }
}

如果某一個(gè)程序不能工作,是因?yàn)槟承┚€程無法向其他線程那樣獲得足夠的CPU時(shí)間,那么,不要企圖通過調(diào)用Thread.yield來修正該程序。

對(duì)于大多數(shù)程序員來說,Thread.yield的唯一用途是在測(cè)試期間人為地增加程序的并發(fā)性。通過探查程序中更大部分的狀態(tài)空間,可以發(fā)現(xiàn)一些隱蔽的Bug。

73 避免使用線程組

除了線程、鎖和監(jiān)視器之外,線程系統(tǒng)還提供了一個(gè)基本的抽象,即線程組。

線程組并沒有提供太多有用的功能,而且他們提供的許多功能還都是有缺陷的。


個(gè)人介紹:

高廣超:多年一線互聯(lián)網(wǎng)研發(fā)與架構(gòu)設(shè)計(jì)經(jīng)驗(yàn),擅長設(shè)計(jì)與落地高可用、高性能、可擴(kuò)展的互聯(lián)網(wǎng)架構(gòu)。

本文首發(fā)在 高廣超的簡(jiǎn)書博客 轉(zhuǎn)載請(qǐng)注明!

簡(jiǎn)書博客
頭條號(hào)
最后編輯于
?著作權(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)容