1. 程式人生 > >【Java多執行緒】執行緒同步機制

【Java多執行緒】執行緒同步機制

執行緒同步是為了確保執行緒安全,所謂執行緒安全指的是多個執行緒對同一資源進行訪問時,有可能產生資料不一致問題,導致執行緒訪問的資源並不是安全的。如果多執行緒程式執行結果和單執行緒執行的結果是一樣的,且相關變數的值與預期值一樣,則是執行緒安全的。

Java中與執行緒同步有關的關鍵字/類包括:

volatile、synchronized、Lock、AtomicInteger等concurrent包下的原子類。。。等

接下來討論這幾種同步方法。

volatile

volatile一般用在多個執行緒訪問同一個變數時,對該變數進行唯一性約束,volatile保證了變數的可見性,不能保證原子性。

用法(例):private volatile booleanflag = false

保證變數的可見性:volatile本質是告訴JVM當前變數線上程暫存器(工作記憶體)中的值是不確定的,需要從主存中讀取,每個執行緒對該變數的修改是可見的,當有執行緒修改該變數時,會立即同步到主存中,其他執行緒讀取的是修改後的最新值。

不能保證原子性:原子性指的是不會被執行緒排程機制打斷的操作,在java中,對基本資料型別的變數的讀取和賦值操作是原子性操作。自增/自減操作不是原子性操作。例如:i++,其實是分成三步來操作的:1)從主存中讀取i的值;2)執行+1操作;3)回寫i的值。volatile關鍵字並不能保證原子性操作。非原子操作都會產生執行緒安全的問題,那麼如何實現自增/自減的原子性呢?後續將有講解。

synchronized

synchronized提供了一種獨佔的加鎖方式,是比較常用的執行緒同步的關鍵字,一般在“執行緒安全的單例”中普遍使用。該關鍵字能夠保證程式碼塊的同步性和方法層面的同步。

用法(例):1)程式碼塊同步

//使用synchronized關鍵字實現執行緒安全的單例模式
    private static Singleton instance;
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class)
            {
                if(instance == null){
                    instance = new Singleton();
                }              
            }
        }
        return instance;
}
privateSingleton(){ }

2)方法同步

public static synchronized Singleton getInstance2(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
private Singleton(){ }

很多人說synchronized在效能上存在較大問題,但並沒有真實環境產生的資料比較說明,因此在這裡不好討論效能問題。

volatile和synchronized的區別

1)      volatile通過變數的可見性,指定執行緒必須從主存中讀取變數的最新值;synchronized通過阻塞執行緒的方式,只有當前執行緒能訪問該變數,鎖定了當前變數。

2)      volatile使用在變數級別;synchronized可以使用在變數、方法、類級別

3)      volatile不會造成執行緒阻塞;synchronized可能會造成執行緒阻塞

4)      volatile不能保證原子性;synchronized能保證原子性

5)      volatile標記的變數不會被編譯器優化;synchronized標記的變數有可能會被編譯器優化(指令重排)。

如何保證自增/自減的原子性

1)      使用java.util.concurrent包下提供的原子類,如AtomicInteger、AtomicLong、AtomicReference等。

用法:   

AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement();//實現原子自增
atomicInteger.getAndDecrement();//實現原子自減

2)使用synchronized同步程式碼塊

Synchronized(this){
      value++;
}

3)使用Lock顯示鎖同步程式碼塊

 private Lock lock = new ReentrantLock();
    private final int incrementAndGet(){
        lock.lock();
        try
        {
            return value++;
        }
        finally
        {
            // TODO: handle finally clause
            lock.unlock();
        }
    }