1. 程式人生 > >java多執行緒之volatile關鍵字

java多執行緒之volatile關鍵字

在java執行緒併發處理中,關鍵字volatile的主要作用是使變數在多個執行緒間可見。那麼volatile到底該怎麼用了?我們首先來看一段程式碼:

public class MyThread1 implements Runnable {
	private boolean istag = true;

	public boolean isIstag() {
		return istag;
	}
	public void setIstag(boolean istag) {
		this.istag = istag;
	}

	public void print() {
		try {
			while (istag) {
				System.out.println("print()執行緒名稱是:"+Thread.currentThread().getName());
				Thread.sleep(1000);
			}
		} catch (Exception e) {
		}
	}

	@Override
	public void run() {
		print();
	}
}

public class MyThread {
	public static void main(String[] args) {
		MyThread1 m=new MyThread1();
		new Thread(m).start();
		System.out.println("我要停止迴圈-->"+Thread.currentThread().getName());
		m.setIstag(false);
	}
}

此段程式碼如果執行,我們可以發現執行緒能正常停止。如果程式碼執行在-server伺服器模式中64bit的JVM上時,會出現死迴圈。解決的方案是用volatile關鍵字。關鍵字volatile的作用是強制從公共堆疊取得變數的值,而不是從執行緒私有資料棧中取得變數的值。

我們將程式碼改造下:

public class MyThread1 extends Thread {
	private volatile boolean istag = true;

	public boolean isIstag() {
		return istag;
	}
	public void setIstag(boolean istag) {
		this.istag = istag;
	}

	@Override
	public void run() {
		System.out.println("進入run()====");
		while (istag) {
			System.out.println("執行緒名稱是:"+Thread.currentThread().getName());
		}
		System.out.println("執行緒被停止====");
	}
}


使用了volatile關鍵字增加了例項變數在多執行緒之間的可見性。但volatile關鍵字最致命的缺點是不支援原子性。

關鍵字synchronized跟volatile進行一下比較:

  1. 關鍵字volatile是執行緒同步的輕量級實現,所以volatile效能肯定比synchronized要好,並且volatile只能修飾變數,而synchronized可以修飾方法,以及程式碼塊。隨著JDK新版本的釋出,synchronized關鍵字執行效率打打的提高,在開發中使用synchronized還是比較多的。
  2. 多執行緒訪問volatile不會發生堵塞,而synchronized可能會出現堵塞。
  3. volatile能保證資料的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有記憶體和公共記憶體中的資料做了同步。
  4. 關鍵字volatile解決的是變數在多個執行緒之間的可見性;而synchronized關鍵字解決的是多個執行緒之間訪問資源的同步性。
volatile非原子的特性
public class MyThread1 extends Thread {
	private volatile static int count;
	public void addCount(){
		for (int i = 0; i < 100; i++) {
			count++;
		}
		System.out.println("count="+count);
	}
	@Override
	public void run() {
		addCount();
	}
}

public class RunTest {
	public static void main(String[] args){
		MyThread1[] myArray=new MyThread1[100];
		for (int i = 0; i < 100; i++) {
			myArray[i]=new MyThread1();
		}
		for (int i = 0; i < 100; i++) {
			myArray[i].start();
		}
	}
}


public class MyThread1 extends Thread {
	private static int count;
	//需要加static,才達到同步效果
	public synchronized static void addCount(){
		for (int i = 0; i < 100; i++) {
			count++;
		}
		System.out.println("count="+count);
	}
	@Override
	public void run() {
		addCount();
	}
}


關鍵字volatile主要使用的場合是在多執行緒中可以感知例項變數被更改了,並且可以獲得最新的值使用,也就是用多執行緒讀取共享變數時可以獲得最新的值。

關鍵字volatile提示執行緒每次從共享記憶體中讀取變數,而不是從私有記憶體中讀取,這樣就保證了同步資料的可見性。但需要注意的是:如果修改例項變數中的資料,比如i++,也就是i=i+1,這樣的操作並不是一個原子操作,也就是非執行緒安全的。表示式i++的實際操作步驟是:

  1. 從記憶體中讀取i的值;
  2. 計算i的值;
  3. 將i的值寫入到記憶體中。
所以說volatile本身並不處理資料的原子性,而是強制對資料讀寫及時影響到主記憶體的。 由上我們可以得出結論: synchronized關鍵字是防止多個執行緒同時執行一段程式碼,那麼就會很影響程式執行效率,而volatile關鍵字在某些情況下效能要優於synchronized,但是要注意volatile關鍵字是無法替代synchronized關鍵字的,因為volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下2個條件:
  1)對變數的寫操作不依賴於當前值
  2)該變數沒有包含在具有其他變數的不變式中


  實際上,這些條件表明,可以被寫入 volatile 變數的這些有效值獨立於任何程式的狀態,包括變數的當前狀態。
  事實上,我的理解就是上面的2個條件需要保證操作是原子性操作,才能保證使用volatile關鍵字的程式在併發時能夠正確執行。

Java中使用volatile的場景:
package com.ztz.myThread;

public class Singleton {
	private volatile static Singleton instance = null;

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