java多線程-volatile的使用

volatile關鍵字

主要做用:java

​ 1.保證數據之間的可見性。segmentfault

​ 2.禁止指令重排序。多線程

1.可見性

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.多線程內存模型優化

volatile內存模型.png

3.爲何?spa

從第一幅圖咱們能夠看出,各一個線程都有一個工做內存,線程運行時,他會從主內存讀取數據到工做內存,而後在使用工做內存,執行完在save到工做內存.可是其餘線程感知不到主內存的變化,不知道主內存的flag變成了false,因此沒有更新本身的工做空間中的flag(由於沒有操做讓他去主內存讀取數據),致使flag爲true,因此循環沒法終止.線程

4.volatile的做用:強制讓其讀取主內存,而不是工做空間,多個線程使用的爲同一個空間,就保證了可見性.code

5.volatile保證了可見性可是卻沒有保證原子性.須要其餘操做來實現。

若是將flag的volatile添加上。

結果:

Thread-1結束
Thread-0結束
main結束
flag=false

2.禁止指令重排序(有序性)

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同時爲零的狀況。這表示某個線程的指令沒有按照順序執行,順序被打亂了。

相關文章
相關標籤/搜索