「 天行健,君子以自強不息。地勢坤,君子以厚德載物。」———《易經》html
volatile 變量,在上一篇文章中已經有簡單說起相關概念和用法,這一篇主要對 Volatile 變量的特性進行源碼驗證。驗證它的涉及到的三個特性:java
#volatile 之可見性驗證 上一篇文章中,講到 volatile 變量一般被當作狀態標記使用。其中典型的應用是,檢查標記狀態,以肯定是否退出循環。下面咱們直接舉個反例,源碼以下:安全
public class Volatile { boolean ready=true; //volatile 狀態標誌變量 private final static int SIZE = 10; //建立10個對象,可改變 public static void main(String[] args) throws InterruptedException{ Volatile vs[]=new Volatile[SIZE]; for(int n=0;n<SIZE;n++) (vs[n]=new Volatile()).test(); System.out.println("mainThread end");//調用結束打印,死循環時不打印 } public void test() throws InterruptedException{ Thread t2=new Thread(){ public void run(){ while(ready);//變量爲true時,讓其死循環 } }; Thread t1=new Thread(){ public void run(){ ready=false; } }; t2.start(); Thread.yield(); t1.start(); t1.join();//保證一次只運行一個測試,以此減小其它線程的調度對 t2對boolValue的響應時間 的影響 t2.join(); } }
其中,ready 變量是咱們要驗證的 volatile 變量。一開始 ready 初始化爲 true,其次啓動 t2 線程讓其進入死循環;接着,t1 線程啓動,而且讓 t1 線程先執行,將 ready 改成 false。理論上來說,此時 t2 線程應該跳出死循環,可是實際上並無。此時 t2 線程讀到的 ready 的值仍然爲 true。因此這段程序一直沒有打印出結果。這即是多線程間的不可見性問題,官方話術爲: 線程 t1 修改後的值對線程 t2 來講並不可見。下圖能夠看到程序一直處於運行狀態:微信
解決辦法是:對變量 ready 聲明爲 volatile,再次執行者段程序,可以順利打印出 「mainTread end」。volatile 保證了變量 ready 的可見性。多線程
另外補充說明我這個例子用的 Java 版本:併發
#volatile 之重排序問題說明 有序性:表示程序的執行順序按照代碼的前後順序執行。經過下面代碼,咱們將更加直觀的理解有序性。jvm
int a = 1; int b = 2; a = 3; //語句A b = 4; //語句B
上面代碼,語句 A 必定在語句 B 以前執行嗎? 答案是否認的。由於這裏可能發生指令重排序。語句 B 可能先於語句 A 先自行。ide
什麼是指令重排序?處理器爲了提升運行效率,可能對輸入代碼進行優化,他不保證程序中各個語句的執行前後順序同代碼中的順序一致,可是他會保證程序最終執行的結果和代碼順序執行的結果是一致的。測試
可是下面這種狀況,語句 B 必定在 語句 A 以後執行。優化
int a = 1; int b = 2; a = 3; //語句A b = a + 3; //語句B
緣由是,變量 b 依賴 a 的值,重排序時處理器會考慮指令之間的依賴性。
固然,這個 volatile 有什麼關係呢? volatile 變量能夠必定程度上保證有序性,volatile 關鍵字禁止指令重排序。
//x、y爲非volatile變量 //flag爲volatile變量 x = 1; //語句1 y = 2; //語句2 flag = true; //語句3 x = 3; //語句4 y = 4; //語句5
這裏要說明的是,flag 爲 volatile 變量;能保證
以上,就是關於 volatile 的禁止重排序的說明。、
#volatile 之非原子性問題驗證 volatile 關鍵字並不能保證原子性,如自增操做。下面看一個例子:
public class Volatile{ private volatile int count = 0; public static void main(String[] args) { final Volatile v = new Volatile(); for(int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub v.count++; } }).start(); } while(Thread.activeCount() > 1) Thread.yield(); System.out.println(v.count); } }
這個程序執行的結果並無達到咱們的指望值,1000。而且每次的運行結果可能都不同,以下圖,有多是 997 等。
來看下面一副圖,分解自增操做的步驟。
可是,這一系列的操做並非原子的。也就是在 read&load 以後,若是主內存 count 發生變化,線程工做內存中的值因爲已經加載,不會產生對應的變化。因此計算出來的結果和咱們預期不同。
對於 volatile 修飾的變量,jvm 虛擬機只是保證從主內存加載到線程工做內存中的值是最新的。
因此,假如線程 A 和 B 在read&load 過程當中,發現主內存中的值都是5,那麼都會加載這個最新的值 5。線程 A 修改後寫到主內存,更新主內存的值爲6。線程 B 因爲已經 read & load,注意到此時線程 B 工做內存中的值仍是5, 因此修改後也會將6更新到主內存。
那麼兩個線程分別進行一次自增操做後,count 只增長了1,結果也就錯了。
固然,咱們能夠經過併發安全類AomicInteger, 內置鎖 sychronized,顯示鎖 ReentrantLock,來規避這個問題,讓程序運行結果達到咱們的指望值 1000.
1)採用併發安全類 AomicInteger 的方式:
import java.util.concurrent.atomic.AtomicInteger; public class Volatile{ private AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) { final Volatile v = new Volatile(); for(int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub v.count.incrementAndGet(); } }).start(); } while(Thread.activeCount() > 1) Thread.yield(); System.out.println(v.count); } }
2) 採用內置鎖 synchronized 的方式:
public class Volatile{ private int count = 0; public static void main(String[] args) { final Volatile v = new Volatile(); for(int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub synchronized (this) { v.count++; } } }).start(); } while(Thread.activeCount() > 1) Thread.yield(); System.out.println(v.count); } }
3)採用顯示鎖的方式
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Volatile{ private int count = 0; Lock lock = new ReentrantLock(); public static void main(String[] args) { final Volatile v = new Volatile(); for(int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub v.lock.lock(); try { v.count++; }finally { v.lock.unlock(); } } }).start(); } while(Thread.activeCount() > 1) Thread.yield(); System.out.println(v.count); } }
#參考
http://www.cnblogs.com/dolphin0520/p/3920373.html http://www.javashuo.com/article/p-plzfocdq-gv.html https://blog.csdn.net/xilove102/article/details/52437581
本文原創首發於微信公衆號 [ 林裏少年 ],歡迎關注第一時間獲取更新。