跳到主要內容

發表文章

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

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只...