java併發volatile的使用

關鍵字volatile可以說是java虛擬機提供的最輕量級的同步機制。

當一個變量定義爲volatile之後,它就具備兩種特性:

1、保證此變量對所有線程的可見性,即當一個線程修改了這個變量的值,新值對於其他線程是立即可得的。

注:關於volatile的可見性經常被開發人員誤解,覺得:volatile變量對所有線程是立即可見的,所以volatile變量在各個線程中是一致的,所以基於volatile變量的運算在併發 下是安全的。volatile變量在各個線程的工作內存中不存在不一致的問題(在各個線程的工

作內存中,volatile變量也可以存在不一致的情況,但由於每次使用之前都要 先刷新,執行引擎看不到不一致的情況,因此可以認爲不存在不一致的問題) 但是Java裏面的運算並非原子操作,導致volatile變量的運算在併發下一樣是不安全的。如程序1-1

public class VolatileTest {
	public static volatile int race = 0;
	
	public static void increase() {
		race++;
	}
	
	private static final int THREAD_COUNT = 20;
	
	public static void main(String[] args) {
		Thread[] threads = new Thread[THREAD_COUNT];
		for(int i=0;i<THREAD_COUNT;i++) {
			threads[i] = new Thread(new Runnable(){
				@Override
				public void run() {
					// TODO Auto-generated method stub
					for(int i=0;i<10000;i++) {
						increase();
					}
				}
			});
			threads[i].start();
		}
		while(Thread.activeCount() > 1) {
			Thread.yield();
		}
		System.out.println(race);
	}
}
程序1-1


如果併發正確,結果應該爲200000,但結果是一個小魚200000的數字。

問題在於race++我們用javap反編譯之後,得到程序1-2


程序1-2


可以看出,race++操作分程了4條指令,首先getstatic將race的值取到操作棧頂,volatile保證了race的值此時是正確的,但是執行iconst_1,iadd這些指令的時候,其他線程可能已經把race值加大了,棧頂的值此時已過期,所以putstatic執行後就可能把較小的race值同步回主內存之中。

在不符合一下兩條規則的運算場景中,我們仍然要通過加鎖來保證原子性

1、運算結果並不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值

2、變量不需要與其他的狀態變量共同參與不變約束



2、第二個語義是禁止指令重排序優化

Map configOptions;
	char[] configText;
	volatile boolean initialized = false;
	
	class thread1 implements Runnable {
		@Override
		public void run() {
			configOptions = new HashMap<>();
			//模擬讀取配置信息,當讀取完成後將initialized設置爲true以通知其他線程配置可用
			configText = readConfigureFile();
			initialized = true;
		}
	}
	
	class thread2 implements Runnable{
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(!initialized) {
				//initialized爲false的時候睡眠
				Thread.sleep();
			}
			//使用初始化好的配置信息
			doSomethingWithConfig();
		}
	}
	
	//模擬讀取配置信息方法
	private char[] readConfigureFile() {
		//讀取配置信息
		return null;
	}
	//模擬使用配置信息的方法
	private void doSomethingWithConfig() {
		//使用初始化好的配置信息
	}
程序2-1


在程序2-1中的語義爲執行完線程thread1中的讀取配置文件操作後,將initialized狀態設置爲true,線程thread2判斷initialized狀態爲true的時候,對讀取到的配置文件進行處理。而這時候如果沒有對initialized變量添加volatile修飾,就可能會由於指令重排序的優化,導致位於thread1中最後一句的initialized=true被提前執行,這樣thread2中使用配置信息的代碼就可能出現錯誤,而volatile關鍵字則可以避免此類情況的發生。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章