主要做用:java
1.保證數據之間的可見性。segmentfault
2.禁止指令重排序。多線程
2.作個小的測試jvm
public class VolatileTest implements Runnable { //當爲false時線程結束 private static /*volatile*/ boolean flag = true; private static int value = 100; @Override public void run() { // TODO Auto-generated method stub while(flag) { value++; //System.out.println(value);//能夠取消註釋試一試 } System.out.println(Thread.currentThread().getName()+"結束"); } public static void main(String[] args) throws InterruptedException { new Thread(new VolatileTest() ).start(); Thread.sleep(1000); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub flag = false; System.out.println(Thread.currentThread().getName()+"結束"); } }).start(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"結束"); System.out.println("flag="+flag); } }
結果:ide
Thread-1結束 main結束 flag=false
咱們能夠發現,第二個線程將flag改爲false,可是第一個線程並無中止運行。測試
1.多線程內存模型優化
3.爲何?spa
從第一幅圖咱們能夠看出,各一個線程都有一個工做內存,線程運行時,他會從主內存讀取數據到工做內存,而後在使用工做內存,執行完在save到工做內存.可是其餘線程感知不到主內存的變化,不知道主內存的flag變成了false,因此沒有更新本身的工做空間中的flag(由於沒有操做讓他去主內存讀取數據),致使flag爲true,因此循環沒法終止.線程
4.volatile的做用:強制讓其讀取主內存,而不是工做空間,多個線程使用的爲同一個空間,就保證了可見性.code
5.volatile保證了可見性可是卻沒有保證原子性.須要其餘操做來實現。
若是將flag
的volatile添加上。
結果:
Thread-1結束 Thread-0結束 main結束 flag=false
volatile禁止jvm和處理器對volatile修飾的指令進行重排序,可是修飾符前和後的指令沒有明確的規定。
何爲重排序:
在單線程下:jvm爲了提升執行的效率,會對咱們的代碼進行優化,對咱們的指令進行位置的更換,可是更換有個前提,就是在單線程下邏輯不變,好比:
int a = 1; int b = 2; int c = a + b; //更換爲 int b = 2; int a = 1; int c = a + b;
這種更換不會改變結果(單線程下)。
同時對於cpu來講,爲了知足效率問題,也會對咱們的指令進行重排序,提升cpu流水線
的效率。
重排序規則:
int a = 1; int b = 2; volatile int c = 3; int d = 4; int f = 6;
volatile能夠禁止重排序,可是隻針對修飾的命令,對於上面的程序,a,b沒有修飾,因此,a,b能夠重排序,同時d,f也能夠,可是ab和df是不會進行重排序的,由於volatile生成內存屏障
。
(1)volatile寫操做前面插入一個StoreStore屏障。確保在進行volatile寫以前前面的全部普通的寫操做都已經刷新到了內存。
(2)volatile寫操做後面插入一個StoreLoad屏障。避免volatile寫操做與後面可能存在的volatile讀寫操做發生重排序。
(3)volatile讀操做後面插入一個LoadLoad屏障。避免volatile讀操做和後面普通的讀操做進行重排序。
(4)volatile讀操做後面插入一個LoadStore屏障。避免volatile讀操做和後面普通的寫操做進行重排序。
簡單來講,volatile修飾的前面的指令不會和後面的指令進行重排序,同時運行volatile修飾時,前面的代碼所有執行完畢。
舉個重排序的例子:
public class Disorder { private static int x = 0, y = 0; private static int a = 0, b = 0; public static void main(String[] args) throws InterruptedException { int count = 0; long start = System.currentTimeMillis(); while(true){ count++; x = 0; y = 0; a = 0; b = 0; Thread one = new Thread(() -> { a = 1; x = b; }); Thread other = new Thread(() -> { b = 1; y = a; }); one.start();other.start(); one.join();other.join(); if (x == 0 && y ==0){ long end = System.currentTimeMillis(); System.out.println("程序運行次數:"+count); System.out.println("程序耗時:"+(end-start)); break; } } } }
加入,咱們認爲程序是一行一行執行的,即順序不會發生改變。
狀況1(one) | 狀況1(other) | 狀況2(one) | 狀況2(other) | 狀況3(one) | 狀況3(other) | |
---|---|---|---|---|---|---|
a = 1 | a = 1 | a = 1 | ||||
x = b | b = 1 | b = 1 | ||||
b = 1 | x = b | y = a | ||||
y = a | y = a | x = b | ||||
結果 | a=1 x=0 |
b=1 y=1 |
a=1 x=1 |
b=1 y=1 |
a=1 x=1 |
b=1 y=1 |
狀況4(one) | 狀況4(other) | 狀況5(one) | 狀況5(other) | 狀況6(one) | 狀況6(other) | |
b = 1 | b = 1 | b = 1 | ||||
a = 1 | a = 1 | y = a | ||||
x = b | y = a | a = 1 | ||||
y = a | x = b | x = b | ||||
結果 | a=1 x=1 |
b=1 y=1 |
a=1 x=1 |
b=1 y=1 |
a=1 x=1 |
b=1 y=0 |
按照上述排序,能夠看出永遠是不會出現x =0 和y = 0同時存在的狀況出現,那麼這個程序就沒有結果。
可是運行程序:
程序運行次數:5130 程序耗時:2453
程序成功退出,表示出現了xy同時爲零的狀況。這表示某個線程的指令沒有按照順序執行,順序被打亂了。