2025-02-15

正文
註意:條目和用語可能與書籍有所出入,但盡量保持原樣加一些自己的理解。
  一、性能
    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天。發現以這種方式來看書也不錯,一方面能加快速度,一方面由於要寫文章更加認真細讀,還能提煉把書讀薄記錄分享出來,實在是很適合我這樣的 🙂

 

摘自 農民伯伯

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *