【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();
}
}