併發編程十:volatile關鍵字

轉載:https://blog.csdn.net/BushQiang/article/details/81349031

當多個線程進行操作共享數據時,可以保證內存中的數據可見。 相較於 synchronized 是一種較爲輕量級的同步策略。
缺點:

  • volatile 不具備“互斥性”
  • volatile 不能保證變量的“原子性”

內存可見性是指當某個線程正在使用對象狀態而另一個線程在同時修改該狀態,需要確保當一個線程修改了對象狀態後,其他線程能夠看到發生的狀態變化。
可見性錯誤是指當讀操作與寫操作在不同的線程中執行時,我們無法確保執行讀操作的線程能適時地看到其他線程寫入的值,有時甚至是根本不可能的。

我們可以通過同步來保證對象被安全地發佈。除此之外我們也可以使用一種更加輕量級的 volatile 變量。

1.例子:
public class TestVolatile {
 
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		new Thread(td).start();
		while (true) {
			if (td.isFlag()) {
				System.out.println("------------------進來");
			}
		}
	}
}
 
class ThreadDemo implements Runnable {
	private boolean flag = false;
	@Override
	public void run() {
		// 延遲一秒
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		flag = true;
		System.out.println("flag=" + isFlag());
	}
	public boolean isFlag() {
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
 
}

flag是共享數據存在於主存中,ThreadDemo啓動以後主要是來改變flag的值,而main函數本身也是一個線程來讀取flag的值。爲了讓效果更加明顯,在ThreadDemo線程裏面run方法裏面休眠了1秒,目的是先讓flag的值先改變,不然main線程裏面可能會先讀取到flag的值。按理說ThreadDemo線程先改變了flag的值爲true,然後while(true)裏面讀取flag的值應該是true,然後輸出,但是運行結果並不是這樣。

運行結果如下:
在這裏插入圖片描述

2. 爲什麼會這樣呢?在這裏插入圖片描述

首先ThreadDemo線程先從主存中讀取共享數據的內容,然後放入自己的工作內存中,然後再改變flag的值,但是這時的值還沒有寫回主存中,main就讀取的flag的值,此時爲false,當ThreadDemo把值寫回去後,但是main函數裏面的while(true)調用的是系統比較底層的代碼,速度快,快到沒有時間再去讀取主存中的值,所以while(true)讀取到的值一直是false。

3. 解決

(1) synchronized鎖

while (true) {
			synchronized (td) {
				if (td.isFlag()) {
					System.out.println("------------------進來");
				}
			}
 
		}

synchronized 實際上是對訪問修改共享變量的代碼塊進行加互斥鎖,多個線程對synchronized代碼塊的訪問時,某一時刻僅僅有一個線程在訪問和修改代碼塊中的內容(加鎖),其他所有的線程等待該線程離開代碼塊時(釋放鎖)纔有機會進入synchronized代碼塊。

某一個線程進入synchronized代碼塊前後,執行過程入如下:
a.線程獲得互斥鎖
b.清空工作內存
c.從主內存拷貝共享變量最新的值到工作內存成爲副本
d.執行代碼
e.將修改後的副本的值刷新回主內存中
f.線程釋放鎖

(2) volatile修飾變量

private volatile boolean flag = false;

volatile如何實現可見性:

volatile變量每次被線程訪問時,都強迫線程從主內存中重讀該變量的最新值,而當該變量發生修改變化時,也會強迫線程將最新的值刷新回主內存中。這樣一來,不同的線程都能及時的看到該變量的最新值。

volatile不能保證變量更改的原子性:

比如sum++,這個操作實際上是三個操作的集合(讀取sum,sum加1,將新的值寫回sum)
volatile只能保證每一步的操作對所有線程是可見的,但是假如兩個線程都需要執行sum++,那麼這一共6個操作集合,之間是可能會交叉執行的,那麼最後導致sum的結果可能會不是所期望的。

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