理解 JVM:Java 內存模型(二)——volatile

概述

java 內存模型的核心是圍繞着在併發過程當中如何處理原子性、可見性、有序性這3個特性來展開的,它們是多線程編程的核心。java

  • 原子性(Atomicity): 是指一個操做是不可中斷的,即便是多個線程同時執行的狀況下,一個操做一旦開始,就不會被其它線程干擾。對於基本類型的讀寫操做基本都具備原子性的(在32位操做系統中 long 和 double 類型數據的讀寫不是原子性的,由於它們有64位)。
  • 可見性(Visibility): 是指在多線程環境下,當一個線程修改了某一個共享變量的值,其它線程可以馬上知道這個修改。
  • 有序性(Ordering): 是指程序的執行順序是按照代碼的前後順序執行的;對於這句話若是在單線程中全部的操做都是有序的,可是在多線程環境下,一個線程的操做相對於另一個線程的操做是無序的。

<!-- more -->編程


先行發生原則(happens-before)

先行發生是 Java 內存模型中定義的兩個操做之間的偏序關係,這些先行關係無需任何同步器的協助就已經存在,能夠在編碼中直接使用。Java 內存模型對這些關係做了以下規則:緩存

  • 程序次序規則(Program Order Rule): 在一個線程內,程序安裝代碼順序執行。即所謂的「線程內表現爲串行的語義(Within-Thread As-If-Serial Semantics)」。
  • 管程鎖定規則(Monitor Lock Rule): 一個 unlock 操做先行發生於後面對同一個鎖的 lock 操做。
  • volatile 變量規則(Volatile Variable Rule): 對於一個 volatile 變量的寫操做先行發生於此線程的每個動做。
  • 線程啓動規則(Thread Start Rule): Thread 對象的 start() 方法先行發生於此線程的每個動做。
  • 線程終止規則(Thread Termination Rule): 線程中的全部操做先行發生於對此線程的終止檢測。
  • 線程中斷規則(Thread Interruption Rule): 對線程 interrupt() 方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生。
  • 對象終結規則(Finalizer Rule): 一個對象的初始化完成先行發生於它的 finalizer() 方法。
  • 傳遞性(Transitivity): 若是操做 A 先行發生於操做 B,操做 B 先行發生於 操做 C,能夠推斷出 操做 A 先行發生於操做 C。

關鍵字 volatile

volatile 修飾的變量保證了不一樣線程對這個變量進行操做時的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。由於當對普通變量進行讀寫的時候,每一個線程先從內存拷貝變量到CPU緩存中。若是計算機有多個CPU,每一個線程可能在不一樣的CPU上被處理,這意味着每一個線程能夠拷貝到不一樣的CPU cache中。而volatile修飾的變量,JVM保證了每次讀變量都從內存中讀,跳過CPU cache這一步。volatile修飾的變量禁止進行指令重排序,因此能在必定程度上保證有序性。只能保證該變量所在的語句仍是原來的位置,並不能保證該語句以前或以後的語句是否被打亂。多線程

volatile 的特性

  1. 當一個變量被 volatile 修飾以後,能保證此變量對全部線程的可見性,即當一個線程修改了這個變量的值,新值對其它線程是當即可見的。
  2. 被 volatile 修飾的變量經過查詢內存屏障來禁止指令重排序,因此能在必定程度上保證有序性。
  3. 對任意單個 volatile 變量的讀/寫具備原子性,但相似於 volatile++ 這種複合操做不具備原子性。 例如:
package com.pdh.test;

/**
 * volatile 複合操做測試
 *
 * @author pengdh
 * @date 2017/11/12
 */
public class VolatileDemo {
	//	申明 volatile 變量
	private static volatile int i = 0;
	//	計數
	private static final int COUNT = 10;

	/**
	 * 對 volatile 變量複合運算
	 */
	private static void increase() {
		i++;
	}

	public static void main(String[] args) {
		// 啓動 10 個線程分別對 i 進行 10000 次計算,正常狀況結果爲 100000 
		for (int j = 0; j < COUNT; j++) {
			new Thread(() -> {
				for (int k = 0; k < 10000; k++) {
					increase();
				}
			}).start();
		}
		// 等待全部累加線程所有執行結束,這裏不一樣 ide 中線程存活數不同,
		// 該示例代碼在 idea 中運行,會多出一個 Monitor Ctrl-Break 線程,故條件是 > 2,
		// 若是在 Eclipse 中條件應爲 > 1
		while (Thread.activeCount() > 2) {
			Thread.yield();
		}
		System.out.println(i);
	}
}

如上代碼正常運行結果應該打印100000,但實際結果基本得不到正確結果。這說明了 volatile 變量的複合運算並不具備原子性,想要獲得正確結果,須要對 volatile 變量運算操做加鎖或者加上同步塊。併發

package com.pdh.test;

/**
 * volatile 複合操做測試
 *
 * @author pengdh
 * @date 2017/11/12
 */
public class VolatileDemo {
	//	申明 volatile 變量
	private static volatile int i = 0;
	//	計數
	private static final int COUNT = 10;

	/**
	 * 對 volatile 變量複合運算,使用 synchronized 同步
	 */
	private static synchronized void increase() {
		i++;
	}

	public static void main(String[] args) {
		// 啓動 10 個線程分別對 i 進行 10000 次計算,正常狀況結果爲 100000
		for (int j = 0; j < COUNT; j++) {
			new Thread(() -> {
				for (int k = 0; k < 10000; k++) {
					increase();
				}
			}).start();
		}
		// 等待全部累加線程所有執行結束,這裏不一樣 ide 中線程存活數不同,
		// 該示例代碼在 idea 中運行,會多出一個 Monitor Ctrl-Break 線程,故條件是 > 2,
		// 若是在 Eclipse 中條件應爲 > 1
		while (Thread.activeCount() > 2) {
			Thread.yield();
		}
		System.out.println(i);
	}
}

volatile 適合場景

volatile適用於不須要保證原子性,但卻須要保證可見性的場景。一種典型的使用場景是用它修飾用於中止線程的狀態標記,如:app

package com.pdh.test;


/**
 * volatile 複合操做測試
 *
 * @author pengdh
 * @date 2017/11/12
 */
public class VolatileDemo {
	//	申明 volatile 變量
	private volatile boolean flag = false;
	//	計數
	private static final int COUNT = 10;

	/**
	 * 使用 volatile 變量做爲線程結束標誌
	 */
	private void start() {
		new Thread(() -> {
			while (!flag) {
				System.out.println("Thread is running");
			}
		}).start();
	}

	private void shutdown() {
		flag = true;
		System.out.println("Thread is stop");
	}

	public static void main(String[] args) throws InterruptedException {
		VolatileDemo demo = new VolatileDemo();
		demo.start();
		Thread.sleep(2000);
		demo.shutdown();
	}
}

使用 volatile 的意義

在只需保證可見性的狀況下,volatile 的同步機制性能要優於鎖。ide


參考文獻

  • <span style="color:red;font-size:14px;font-family:Microsoft YaHei;">深刻理解 Java 虛擬機</span>

歡迎掃一掃關注 程序猿pdh 公衆號!性能

歡迎關注公衆號 程序猿pdh

相關文章
相關標籤/搜索