正文
註意:條目和用語可能與書籍有所出入,但盡量保持原樣加一些自己的理解。
一、性能
1. 先把焦點放在設計、數據結構和算法身上
備註:良好的設計、明智的選擇數據結構和算法可能比高效代碼更重要。
2. 不要依賴編譯器優化技術
3. 理解運行時(runtime)代碼優化
備註:JIT將bytecode於運行時轉換為本地二進制碼,從而提高性能。因此編譯後代碼被執行次數越多,本機代碼生成代價就很合算。
4. 連接字符串使用StringBuffer要比String快,尤其是大量字符串拼接
5. 將對象創建成本降至最小
備註:復用既有對象,不要創建非必要的對象,隻在需要的時候才創建它們。
6. 將同步化(synchronization)降至最低
備註:如果synchronized函數拋出異常,則在異常離開函數之前,鎖會自動釋放。如果整個函數都需要被同步化,為瞭產生體積較小且執行速度較快的代碼,請優先使用函數修飾符,而不是在函數內使用synchronized代碼塊。
7. 盡可能使用stack變量
備註:如果在函數中頻繁訪問成員變量、靜態變量,可以用本地(local)變量替代,最後操作完後再賦值給成員/靜態變量。
8. 盡可能的使用static、final和private函數
備註:此類函數可以在編譯期間被靜態決議(statically resolved),而不需要動態議決(dynamic resolved)。(子類無法覆寫)
9. 類的成員變量、靜態變量都有缺省值,務須重復初始化
備註:記住,本地變量沒有缺省值(例如函數內定義的變量)。
10. 盡可能的使用基本數據類型
備註:如int、short、char、boolean,使得代碼更快更小。
11. 不要使用枚舉器(Enumeration)和迭代器(Iterator)來遍歷Vector
備註:使用for循環+get()
12. 使用System.arraycopy()來復制數組
備註:使用System.arraycopy()代替for循環,可以產生更快的代碼。如:
public void copyArray(int[] src, int[] dest) {
int size = src.length;
System.arraycopy(src, 0, dest, 0, size);
}
System.arraycopy()是以native method實現的,可以直接、高效的移動原始數組到目標數組,因此它執行速度更快。
13. 優先使用數組,然後才考慮Vector和ArrayList,理由:
a). Vector的get()是同步的
b). ArrayList基本上就是一個非線程同步的Vector,比Vector要快
c). ArrayList和Vector添加元素或移除元素都需要重新整理數組。
備註:不要僅僅因為手上有個數不定的數據需要存儲,就毫無選擇的使用Vector或ArrayList。可以考慮創建一個足夠大的數組,通常這樣可能會浪費內存,但性能上的收益可能超過內存方面的代價。
14. 手工優化代碼
a). 剔除空白函數和無用代碼
b). 削減強度
備註:以更高效的操作替換成本昂貴的操作。一個常見的優化手法是使用復式復制操作符(如+=、-=)。
c). 合並常量
備註:將變量聲明為final,使得操作在編譯器就進行。
d). 刪減相同的子表達式
備註:可用一個臨時變量代替重復的表達式。
e). 展開循環
備註:如循環次數少且已知循環次數,可展開去掉循環結構,直接訪問數組元素。缺點是會產生更多代碼。
f). 簡化代數
備註:使用數學技巧來簡化表達式。(例如從1+..+100的問題)
g). 搬移循環內的不變式
備註:循環內不變化的表達式可用移至循環外,不必重復計算表達式。
15. 編譯為本機代碼
備註:將程序的某部分編譯為本機二進制代碼,然後通過JNI訪問。
二、多線程
1. 對於實例(instance)函數,同步機制鎖定的是對象,而不是函數和代碼塊。
備註:函數或代碼塊被聲明為synchronized並非意味它在同一時刻隻能有一個線程執行(同一對象不同線程調用會阻塞)。Java語言不允許將構造函數聲明為synchronized。
2. 同步實例函數和同步靜態函數爭取的是不同的locks。
備註:兩者均非多線程安全,可以使用實例變量進行同步控制,如(byte[] lock = new byte[0]),比其他任何對象都經濟。
3. 對於synchronized函數中可被修改的數據,應使之成為private,並根據需要提供訪問函數。如果訪問函數返回的是可變對象,那麼可以先克隆該對象。
4. 避免無謂的同步控制
備註:過度的同步控制可能導致代碼死鎖或執行緩慢。再次提醒,當一個函數聲明為synchronized,所獲得的lock是隸屬於調用此函數的那個對象。
5. 訪問共享變量時請使用synchronized或volatile
備註:如果並發性很重要,而且不需要更新很多變量,則可以考慮使用volatile。一旦變量被聲明為volatile,在每次訪問它們時,它們就與主內存進行一致化。如果使用synchronized,隻在取得lock和釋放lock時候才一致化。
6. 在單一操作(single operation)中鎖定所有用到的對象
備註:如果某個同步函數調用瞭某個非同步實例函數來修改對象,它是線程安全的。使用同步控制時,一定要對關鍵字synchronized所作所為牢記在心。它鎖定的是對象而非函數或代碼。
7. 以固定而全局性的順序取得多個locks(機制)以避免死鎖。P/181~P/185
備註:嵌入[鎖定順序]需要額外的一些工作、內存和執行時間。
8. 優先使用notifyAll()而非notify()
備註:notify()和notifyAll()用以喚醒處以等待狀態的線程,waite()則讓線程進入等待狀態。notify()僅僅喚醒一個線程。
9. 針對wait()和notifyAll()使用旋轉鎖(spin locks)
備註:旋轉鎖模式(spin-lock pattern)簡潔、廉價,而且能確保等待著某個條件變量的代碼能循規蹈矩。
10. 使用wait()和notifyAll()替代輪詢(polling loops)
備註:調用wait()時會釋放同步對象鎖,暫停(虛懸,suspend)此線程。被暫停的線程不會占用CPU時間,直到被喚醒。如:
public void run()
{
int data;
while(true){
synchronized (pipe) {
while((data = pipe.getDate()) == 0){
try{
pipe.waite();
}
catch(InterruptedException e){}
}
}
//Process Data
}
}
11. 不要對已鎖定對象的對象引用重新賦值。
12. 不要調用stop()和suspend()
備註:stop()中止一個線程時,會釋放線程持有的所有locks,有攪亂內部數據的風險;suspend()暫時懸掛起一個線程,但不會釋放持有的locks,可能帶來死鎖的風險。兩種都會引發不可預測的行為和不正確的行為。
當線程的run()結束時,線程就中止瞭運行。可以用輪詢+變量來控制,如下代碼:
private volatile boolean stop;
public void stopThread()
{
stop = true;
}
public void run()
{
while(!stop){
//Process Data
}
}
註意:這裡使用瞭關鍵字volatile,由於Java允許線程在其 私有專用內存 中保留主內存變量的副本(可以優化),線程1對線程2調用瞭stopThread(),但線程2可能不會及時察覺到stop主內存變量已變化,導致不能及時中止線程。
三、類與接口
1. 實現一個final類(immutable class 不可變類)時,請遵循下列規則:
a). 聲明所有數據為private
b). 隻提供取值函數(getter),不提供賦值函數(setter)
c). 在構造函數中設置有實例數據
d). 如果函數返回、接受引用final對象,請克隆這個對象。
e). 區別淺層拷貝和深層拷貝應用場景。如拷貝Vector需要使用深層拷貝。
2. 實現clone()時記得調用super.clone()
備註:不管是淺層拷貝還是深層拷貝都需要調用super.clone()。
3. 別隻依賴finalize()清理內存以外的資源
備註:finalize()函數隻有在垃圾回收器釋放對象占用的空間之前才會被調用,回收時可能並非所有符合回收條件的對象都被回收,也無法保證是否被調用、何時調用。實現finalize()方法時記得調用super.finalize()。
4. 在構造函數內應避免調用非final函數,以免被覆寫而改變初衷。
結束
書是從朋友那邊借過來的,拿到手也有一段時間,磨磨唧唧好多天才看瞭幾十頁,而餘下部分從上篇文章到這篇文章也不過才3-5天。發現以這種方式來看書也不錯,一方面能加快速度,一方面由於要寫文章更加認真細讀,還能提煉把書讀薄記錄分享出來,實在是很適合我這樣的 🙂
摘自 農民伯伯