關於volatile類型變量的一點思考

被volatile 修飾的變量有以下如下重要特點:在引用這種變量的線程之間,被修飾的變量能夠保證其“可見性”。

也就是說,假如X線程修改了a,這條數據會立即更新到內存區域中,Y線程再讀取的時候也是從內存中讀取,會讀取到a變量更新後的值。反過來,假如a變量不用volatile修飾,那麼當X修改了a變量時,有可能僅僅將a修改後的值保存在了catch中,此時新的a值對X可見對Y不可見,Y值有可能會獲取a的“髒數據”。

說清了這個概念,再看看下面的代碼。功能是兩個線程同時對a進行 +1操作,各加10000遍。

public class IntegerCountTest {
	
	static volatile Integer a = 0;
	
	
	static class MyTask implements Runnable{
		
		CountDownLatch ct;
		
		public MyTask( CountDownLatch ct2) {
			this.ct = ct2;
		}

		@Override
		public void run() {
			for(int i=0;i<10000;i++) {
				a = a+1;

			}
			this.ct.countDown();
		}
	}

	public static void main(String[] arg) {

		CountDownLatch ct = new CountDownLatch(2);
		
		Thread t1 = new Thread(new MyTask(ct));
		Thread t2 = new Thread(new MyTask(ct));
		
		t1.start();
		t2.start();
		
		try {
			ct.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(a);
	}
}

 按直覺將,最終輸出的a應該是20000,但實際a 是一個趨近於20000的隨機數。既然 a被volatile 修飾,能夠保證 在t1 和 t2之間的可見性,那麼爲什麼還會出現這種情況?

因爲 a = a+1;不是一個原子操作。

這行代碼至少會作2個操作,先是獲取a的值,加1生成新的值再賦值給a變量。可能t1 和t2同時運行到 a = a+1這一行,同時獲取了a的舊值x ,然後先後將同一個x+1的值賦值給了x。這樣就導致了這種情況。

怎麼解決?將a = a+1變爲一個原子操作,如下所示,將a定義成AtomicInteger 變量,其中a.incrementAndGet();操作能夠保證+1操作的原子性。

public class IntegerCountTest {
	
	//static volatile Integer a = 0;
	//static Integer a = 0;
	static AtomicInteger a = new AtomicInteger(0);
	
//	private static  sun.misc.Unsafe UNSAFE ;
//	 private static  long aOffset ;
//	static {
//		UNSAFE = sun.misc.Unsafe.getUnsafe();
//		Class<?> k = IntegerCountTest.class;
//		try {
//			aOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("a"));
//		} catch (NoSuchFieldException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		} catch (SecurityException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//	}
	
	static class MyTask implements Runnable{
		
		CountDownLatch ct;
		
		public MyTask( CountDownLatch ct2) {
			this.ct = ct2;
		}

		@Override
		public void run() {
			for(int i=0;i<10000;i++) {
				//a = a+1;
				

//				for(;;) {
//					int temp = a;
//					int newvar = temp+1;
//					if(UNSAFE.compareAndSwapInt(this, aOffset, newvar, temp)) {
//						break;
//					}
//				}

				
				a.incrementAndGet();
			}
			this.ct.countDown();
		}
	}

	public static void main(String[] arg) {

		CountDownLatch ct = new CountDownLatch(2);
		
		Thread t1 = new Thread(new MyTask(ct));
		Thread t2 = new Thread(new MyTask(ct));
		
		t1.start();
		t2.start();
		
		try {
			ct.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(a);
	}
}

當然,還有其他很多方法可以實現,比如加鎖,以及jvm內部使用的UNSAFE方法等。

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