跳到主要內容

發表文章

目前顯示的是有「Effective Java」標籤的文章

Effective Java - Concurrency

這部分收集觀看Effective Java針對Concurrency章節後所整理的心得。 Synchronize access to shared mutable data Avoid excessive synchronization Prefer executors, tasks, and streams to threads Use lazy initialization judiciously Avoid thread groups & Don’t depend on the thread scheduler

Effective Java - Prefer executors, tasks, and streams to threads

這個Item的重點就是要你善用JDK內建的concurrency物件,而不要自己產生與控制thread。書中以JDK內建的Thread Pool ExecutorService來做說明, ExecutorService支援pool size的控制、工作生命週期的控制、pool的銷毀。 Executors提供不同類型的thread pool,單一thread的newSingleThreadExecutor、無大小限制但可能會讓你server爆掉的newCachedThreadPool、有大小限制的newFixedThreadPool、簡單排程的ScheduledThreadPoolExecutor等。 JDK將怎麼執行這件事情model為Runnable與Callable;Runnable屬於無回傳值的執行方法,Callable屬於有回傳值的執行方法。 JDK7以後支援Jorkjoin pool,詳情參考 link 。 Reference: Effective Java, 3/e, Item 80。

Effective Java - Use lazy initialization judiciously

Problem 這個Item是在探討使用Lazy initialization的時機與方法,而在多執行緒存取的時,較容易發生問題。如以下程式要初始化一個Singleton物件,但在多執行緒的情況下,Client可能會拿到不是唯一的instance: public class Singleton {   private static Singleton instance ;   public static Singleton get ( ) { if ( instance == null ) { instance = new Singleton ( ) ; } return instance ; } } Single-check idiom 第一個方法是基於原本問題的改良,適用於能接受重複初始化,會比問題程式碼效能稍微好一些。 public class Singleton {   private static volatile Singleton instance ;   public static Singleton get ( ) { Singleton result = instance ; if ( result == null ) { result = instance = new Singleton ( ) ; } return result ; } } 這裡用result local variable的原因,是為了要讓instance變數在已初始化情況下只存取一次;這是由於volatile宣告會強制Thread在使用instance時,去讀取最新的資料。根據作者所說,效能會差1.4倍。 Normal Initialization 在一般情況下,因為JVM ClassLoader載入類別是Thread-Safe,直接使用以下方法就不會有Multi-thread的問題: private final static Singleton instance = new Singleton ( ) ; 但如果初始化這個物件的效能成本很大,希望能等到有使...

Effective Java - Avoid excessive synchronization

這個Item的宗旨如字面上的意思:「避免過度的使用同步」。這個Item有以下幾個重點: 1. synchronized block中,不要呼叫有機會被client或subclass控制的method,以避免liveness與safety failures 作者提及了alien method這個名稱,在我的理解中,alien method可能以有機會被override的method、client傳進來的function物件或呼叫到可能會引用到類別中變數的物件等方式呈現: private List < Listener > listeners = new ArrayList <> ( ) ;   public void doSomthing ( Listener listener ) { synchronized ( listeners ) { listeners. add ( listener ) ; // call alien method postAction ( listeners ) ; }   } protected abstract void postAction ( List < Listener > listeners ) ; 這樣子的alien method,class本身難以控制client或subclass或做什麼,只要alien method有可能修改到class的變數,就有機會造成safety failures。 2. synchronized block中,為了效能著想,工作越少越好 由於synchronized關鍵字的關係,即使是多執行緒的程式,同時間只有一個能夠進入synchronized block中。假如在可能發生race condition的變數前後,有繁重的工作要執行,如下: public void doSomthing ( Listener listener ) { synchronized ( listeners ) { heavyPreAction ( ) ; listeners.add ( listener ) ; ...

Effective Java - Avoid thread groups & Don’t depend on the thread scheduler

Avoid thread groups 我會特別記錄這個Item,是由於我閱讀Java高併發編程詳解:多線程與架構設計時,有個章節專門在講ThreadGroup的功用;但我想起曾經在Effective Java中,看到不建議使用它,所以我重翻了Effective Java第二版與Java Threads第三版。我針對看到的內容做一下整理分享給大家。 作者認為ThreadGroup的API太弱,沒什麼實質用途,主要為以下幾點原因: ThreadGroup原始目的是用來隔離applet,用以限制Thread是否有能力存取其它Thread的狀態;但這並沒被履行過,且Applet已經被廢了。 stop、suspend、resume被廢了。 在Java 1.5之前,只有ThreadGroup才有能力去處理Thread例外,但現在Thread本身就有。 要取得active的thread必須透過enumerate method,而且因為同步存取的關係,不保證正確性。 在Java Threads第三版中,針對ThreadGroup提到,使用它有兩個好處: 提供你對group中所有的thread便利地一次操作,不過現在會有用的應該只有interrupt。 控制thread狀態存取的安全性,就是Effective Java作者反對的那點。 然而第一點的好處,似乎使用executor的API就能做到了,所以.. 話不能說死,如果以後我真的有使用或看到ThreadGroup使用方法,再分享給大家。 Reference: Effective Java, 2/e, Item 73。 Java高併發編程詳解:多線程與架構設計, 汪文君  Java Threads, 3/e Don’t depend on the thread scheduler 這個Item重點就是要你不要相信thread scheduler可以幫你解決thread排班問題,把code寫好比較實在。有幾個重點要注意: Thread的數量不要大於CPU的核心數。一般Thread數量都會使用CPU核心數+1的公式,但在Java Concurrency In Practice中,有提供根據CPU bound vs IO bound比率去tune thread pool size的方法,大家可以參考。 基於不可靠、不好測試與不好...

Effective Java - Synchronize access to shared mutable data

這個Item主要在宣導「針對多執行緒會共享的變數,應該要Synchronize access;為了提高效能而不使用它,是非常危險且錯誤的。」要能寫出正確且效能好的程式碼,首先必須要了解原子性、有序性、可見性這三個特性: 原子性: 對於共享變數的連續操作,必須確保全部都會被執行且不會被打斷。如果做到一半被打斷,結果當然就不會正確。 可見性: 某一執行緒對共享變數做了修改,另外一個變數在使用時,能夠立即拿到最新的數值。由於每個執行緒都有一份變數的資料備份,如果其中一個執行緒對變數做修改,當沒有做到可見性就會導致其它執行緒拿到舊的值。 有序性: 程式的執行結果必須和程式碼順序的執行結果相同。沒辦法確保有序性,是由於CPU為了提高程式的執行效率,做了代碼重排,在多執行緒時會有機會發生問題。 再來必須了解的是處理方法,最簡單的是直接使用synchronized去做mutual exclusion: synchronized ( mutex_object ) { // operations on the mutex_object } synchronized可以保證上面提到的三個特性,但最大的缺點就是無法被interrupted;如果可以的話,建議使用Lock相關的API。 另外常被誤用的就是volatile。volatile可以確保可見性與有序性,但它無法保證原子性,如果在多執行緒的存取下,一樣有可能造成不正確的結果。 最後一個要提的就是java.util.concurrent.atomic內的Atomic類。它是透過volatile加CAS(Compare and set)來達到thread-safe與lock-free的特性,因此往往能獲得比較好的效能: // Lock-free synchronization with java.util.concurrent.atomic private static final Atomiclong nextSerialNum = new Atomiclong ( ) ; public static long generateSerialNumber ( ) { return nextSerialNum. getAndIncrement ( ) ; } Effective Java只...

Effective Java - Methods

 這篇是收集觀看Effective Java針對Methods章節後所整理的心得。 Override clone judiciously Check parameters for validity & Use varargs judiciously Return empty collections or arrays, not nulls Return optionals judiciously

Effective Java - Return optionals judiciously

這個item主要告訴你使用Java 8推出的Optional的注意事項。本篇文章我以Java開發人員 Stuart Marks演講內容 與 投影片 中所提到的Rules去整理Effective Java與網路上的內容。 Rule#1: Never, ever, use null for an Optional variable or return value. 使用Optional的目的,是希望提供一個機制讓method回傳結果能夠表達“no result”,而不是透過null,因為null造成了很多的bug。以下為原文: Optional is intended to provide a limited mechanism for library method return types where there is a clear need to represent "no result," and where using null for that is overwhelmingly likely to cause errors. - From Stuart Marks' presentation 除此之外,也可以參考Java Language Architect - Brian Goetz的 說法 。 因此,當你讓Optional變數塞了null,或者是直接回傳null,就是脫褲子放屁;而在我們的production code中,還真的有這種code。 另外,Effective Java有提到適合使用Optional當method回傳值的使用時機: 是你的client需要針對空值做特別處理的時候。 Rule#2: Never use Optional.get() unless you can prove that the Optional is present. 第二個rule是要告訴你別直接使用Optional.get()去取得值,除非你確定它一定有值,如果使用sonarlint,會提示你至少要寫成這樣: Optional < User > searchResult = u...

Effective Java - Return empty collections or arrays, not nulls

這個item是要告訴你要回傳空collection或array,而不要回傳null;因為這會讓你client需要多處理null的情況,也多了需要被測試的路徑,而且也不會比較快。以書中的範例來說,針對collections回傳值的處理,會建議你使用以下方式: public List < Cheese > getCheeses ( ) { return new ArrayList <> ( cheesesInStock ) ; } Note. 這裡我不清楚作者為何要使用ArrayList,如果要immutable應該要用Collections.unmodifiableList。 作者提到有些反對這種做法的人,是基於產生empty collection的開銷,所以又提供了另外一種方式: public List < Cheese > getCheeses ( ) { return cheesesInStock. isEmpty ( ) ? Collections . emptyList ( ) : new ArrayList <> ( cheesesInStock ) ; } 透過Collections.emptyList取得的empty collection,是可以減少產生的開銷,但也代表你要多寫單元測試代碼;作者認為如果沒有證據表明這真的是一個效能問題,就請忽略它,以避免增加不必要的困擾。 針對Array的情況也是類似: public Cheese [ ] getCheeses ( ) { return cheesesInStock. toArray ( new Cheese [ 0 ] ) ; } 假如在意new Cheese[0]會造成效能問題,就用空間換時間吧! 另外作者有提到,千萬別寫以下方式,會因而產生效能問題: return cheesesInStock. toArray ( new Cheese [ cheesesInStock. size ( ) ] ) ; Note. 可以參考 此篇 詳細說明。 Reference: Effective Java, 3/e, Item 54。

Effective Java - Check parameters for validity & Use varargs judiciously

Use varargs judiciously varargs在我看這個item之前,我用的習以為常: public String appendStrings ( String ... strs ) { StringBuilder sb = new StringBuilder ( ) ; for ( String str : strs ) { sb. append ( str ) ; } return sb. toString ( ) ; } 後來某天和同事討論到strs輸入檢查的問題時,就回來翻閱了這個item。假如你要限制client必須輸入一個或多個參數時,在method內做限制檢查並不是一個好的設計;書中建議做法可以如下: public static String appendStrings ( String first, String ... strs ) { StringBuilder sb = new StringBuilder ( first ) ; for ( String str : strs ) { sb. append ( str ) ; } return sb. toString ( ) ; } 透過限制必須傳入一個參數的方式,可以降低有人傳空資料的機會。除此之外,書中提到使用這種方式可能會導致效能問題,因為每次的呼叫JVM都會針對內容做array的allocation與initialization。從網路上別人做實驗的文章中發現,速度大概慢了60倍。 針對client的呼叫有95%的機會少於三個參數的人,書中給了以下workaround: public void foo ( ) { } public void foo ( int a1 ) { } public void foo ( int a1, int a2 ) { } public void foo ( int a1, int a2, int a3 ) { } public void foo ( int a1, int a2, int a3, int ... rest ) { } 還特別舉了JDK中的EnumSe...

Effective Java - Override clone judiciously

Introduction 先說結論,這個Item其實是要勸你別Override Object.clone,而去使用Copy Constructor或Factory pattern去做clone。最近會回來重新看這篇,是由於同事override clone被SonarLint給找了出來,逼得我去研究他的寫法倒底是否合理。 既然是勸世文,當然要先從“如何寫正確的clone”開始。 Implement DeepCopy 假如你要複製的物件本身只有primitive的變數,那不會有太大問題,除非變數宣告為final。如果你要複製物件包含了非primitive的變數,但你沒有“特別處理”,那super.clone只會幫你把reference給複製過去,這稱為淺複製,也這意味著修改複製的內容會影響到原本的。 因此針對這些非primitive的變數,你必須一個欄位一個欄位複製。 Declare final class or final clone method 假如你有一個物件實做了clone,但沒宣告成final class or method,代表著你的sub-class有機會override這個clone: class A { public Object clone ( ) { return new A ( ) ; } }   class B extends A { public Object clone ( ) { return super . clone ( ) ; } } 在上面程式碼中,如果你用A去clone沒問題;但如果用B去clone,會因為你的A沒使用Object的clone而導致獲得的物件不是B。因此如果你Override了clone後,最好將其宣告為final。 其它問題 在Override clone時,會因為Object.clone會拋CloneNotSupportedException例外,而要去catch一條不會發生的例外;有final欄位時,你可能會選擇把final拿掉;使用super.clone時,你必須做type cast,也因此你必須對你的parent瞭若指掌。 使用Copy Constructor或Factory pattern ...

Effective Java - Prefer try-with-resources to try-finally

  在Java7以前,針對需要特別close的資源,會寫程式的人基本上都會放在finally的block中: private void close ( Closeable closable ) { try { if ( closable != null ) closable. close ( ) ; } catch ( IOException e ) { // log } }   private void load ( ) { mProp = new Properties ( ) ; InputStream is = null ; try { is = new FileInputStream ( mConfigFilePath ) ; mProp. load ( is ) ; } catch ( IOException e ) { // need to handle .. } finally { close ( is ) ; } } 這個item要強調的是,在Java7之後,使用try-with-resources會讓你的程式碼更精簡: try ( InputStream is = new FileInputStream ( mConfigFilePath ) ) { mProp. load ( is ) ; } catch ( IOException e ) { // need to handle .. } 針對使用的說明,我之前已有文章做分享。這種寫法除了讓你程式碼更精簡外,以上述例子來說,如果load與close都發生例外,是可以透過Exception的getSuppressed去取得這兩個Exceptio...

Effective Java - Lambdas and Streams

這裡收集觀看Effective Java針對Lambads and Streams章節後所整理的心得,其中不包含Item 45: Use streams judiciously;主要原因是這個章節是在強調「別濫用stream而讓程式碼更難以閱讀」,建議做法在後續的item有提及,因此這裡就不多贅述。以下為我整理的內容: Prefer lambdas to anonymous classes Prefer method references to lambdas Favor the use of standard functional interfaces Prefer Collection to Stream as a return type Prefer side-effect-free functions in streams Use caution when making streams parallel

Effective Java - Use caution when making streams parallel

Introduction 在Java 8中,你只要用以下寫法,很容易就可以讓你的工作並行化: list. stream ( ) . parallel ( ) .. list . parallelStream ( ) .. 但這個item要強調的是: 千萬別亂用!! (以下內容我結合Java 8 Lambdas技術手冊做補充) Structure Good 能獲得較佳效能的結構有ArrayList、HashMap、HashSet、ConcurrentHashMap、arrays、int ranges與long ranges。這是由於它們的結構能夠容易被切割。書中還有提到Prefer SplittableRandom than ThreadLocalRandom,但這部分我沒使用過,無法做深入說明。 Bad 這通常是無法容易被切割的結構,像是LinkedList,我就有遇過同事使用LinkedList做parallel();再來就是使用Stream.iterate與BufferedReader.lines所產生的stream,因為初始長度未知。另外容易被忽略的就是boxing和un-boxing轉換的效能問題,如果可以要盡量減少auto-boxing的操作。 Operations Good 能獲得較佳效能的terminal operations包含reduce、min、max、count與sum;short-circuiting operations包含anyMatch、allMatch與noneMatch。(short-circuiting operations代表著滿足部分條件就可以停止繼續做下去,中文為短路操作,詳細範例可以參考 link ) 除此之外,還有一個重點就是操作是否有狀態,有狀態就會有維持狀的成本與限制;無狀態操作包含map、filter與flatMap。 Bad 有狀態操作包含了sorted、distinct與limit。其中limit在effective java中是特別被列為不適用,這是由於預設的並行化流程並無法精準的知道該怎麼正確處理limit的邏輯。 collect也不適合用在parallel的情況中,因為組合集合開銷非常大;我們大都使用parallel在物件的轉換上,此時資料量會是決定你效能的一個因素。 Pool size 根據 Jav...

Effective Java - Prefer side-effect-free functions in streams

  這個item要強調的是「stream中間處理過程所使用的function,應都屬於side-effect-free function」。side-effect-free function又稱為pure function,它意味著以下兩點: f(x) = y,相同的x永遠都會產生相同的y;且x不能因經過這個函式後,就發生改變。 此函式不會影響到函式以外的變數,也不會受到函式以外的變數影響。(我想const變數應不在此範圍內) 這樣的function會比較好維護,因為當你refactor時,它應不會“意外地”影響到你類別中的狀態;如果與函式以外的變數互動非常頻繁,這個function應難以修改與重複被使用。書中提及有問題的寫法如下: Map < String , Long > freq = new HashMap <> ( ) ; try ( Stream < String > words = new Scanner ( file ) . tokens ( ) ) { words. forEach ( word -> { freq. merge ( word. toLowerCase ( ) , 1L, Long :: sum ) ; } ) ; } 這個寫法濫用了stream,因為它並沒有從stream中獲得容易閱讀的好處;而且forEach適用於回報計算結果,而不是用於執行計算。修改完的結果如下: Map < String , Long > freq ; try ( Stream < String > words = new Scanner ( file ) . tokens ( ) ) { freq = words . collect ( groupingBy ( String :: toLowerCase, counting ( ) ) ) ; } 這裡示範了collector與method reference,讓你的code稍微清晰,但沒寫過Lambda的人可能還是看不懂這在幹嘛。這個item的重點之一就是要你去弄清楚collector toList、toSet、toMap、groupingBy和join的用法。以後有時...