首頁  >  文章  >  Java  >  深入理解volatile關鍵字

深入理解volatile關鍵字

(*-*)浩
(*-*)浩轉載
2019-09-07 16:44:453068瀏覽

深入理解volatile關鍵字

深入理解volatile關鍵字

1.volatile與可見性

都知道volatile可以保證可見性,那到底是如何保證的呢?

這便於Happen-before原則有關,該原則的第三條規定:對一個volatile修飾的變量,寫入操作要早於對這個變量的讀取操作。具體步驟如下:

A執行緒將共享變數讀進工作記憶體中,同時B執行緒也會將共享變數讀進工作記憶體中。

在A執行緒對共享變數修改後,會立即刷新到主內存,此時B線程的工作內存中的共享變數就會被設定無效,需要從主內存重新讀取新值。反映到硬體上就是CPU的Cache line 置為無效狀態。

這樣便保證了可見性,簡單而言,就是線程在對volatile修飾的變量修改且刷新到主內存之後,會使得其它線程的工作內存中的共享變量無效,需要從主內存中再此讀取。

2.volatile與有序性

都知道volatile可以保證有序性,那麼到底是如何保證的呢?

volatile保證有序性,比較直接,禁止JVM和處理器對volatile關鍵字修飾的變數進行指令重排序,但對於該變數之前或之後的可以任意排序,只要最終的結果與沒更改前的結果保持一致即可。

底層原理

被volatile修飾的變數在底層會加上一個「lock:」的前綴,「lock」字首的指令相當於記憶體屏障,這正是保證可見性與有序性的關鍵,而這個屏障的作用主要有一下幾點:

指令重排時,屏障前的程式碼不能重排到屏障後,屏障後的也不能重排到屏障前。

執行到記憶體屏障時,確保前面的程式碼都已經執行完畢,且執行結果是對屏障後的程式碼可見的。

強制將工作記憶體中的變數刷新到主記憶體。

其它執行緒的工作記憶體的變數會設定無效,需要重現從主記憶體讀取。

3.volatile與原子性

都知道volatile不能保證原子性,那為何不能保證原子性呢?

程式碼示範:

package com.github.excellent01;

import java.util.concurrent.CountDownLatch;

/**
 * @auther plg
 * @date 2019/5/19 9:37
 */
public class TestVolatile implements Runnable {
    private volatile Integer num = 0;
    private static CountDownLatch latch = new CountDownLatch(10);
    @Override
    public void run() {
        for(int i = 0; i < 1000; i++){
            num++;
        }
        latch.countDown();
    }

    public Integer getNum() {
        return num;
    }

    public static void main(String[] args) throws InterruptedException {
        TestVolatile test = new TestVolatile();
        for(int i = 0; i < 10; i++){
            new Thread(test).start();
        }
        latch.await();
        System.out.println(test.getNum());
    }
}

啟動10個線程,每個線程對共享變數num,加1000次,當所有的線程執行完畢之後,列印輸出num 的最終結果。

深入理解volatile關鍵字

#很少有10000的這便是因為volatile無法保證原子性造成的。

原因分析:

num 的操作由三個步驟組成:

從主記憶體將num讀進工作內存中

在工作記憶體中進行加一

加一完成後,寫回主記憶體。

雖然這三個步驟都是原子操作,但合起來不是原子操作,每一步執行的過程中都有可能被打斷。

假設此時num的值為10,線程A將變數讀進自己的工作內存中,此時發生了CPU切換,B也將num讀進自己的工作內存,此時值也是10 .B線程在自己的工作內存中對num的值進行修改,變成了11,但此時還沒有刷新到主內存,因此A線程還不知道num的值已經發生了改變,之前所說的,修改volatile變數後,其它線程會立即得知,前提也是要先刷新到主記憶體中,這時,其它線程才會將自己工作中的共享變數的值設為無效。因為沒有刷新到主內存,因此A傻傻的不知道,在10的基礎上加一,因此最終雖然兩個線程都進行了加一操作,但最終的結果只加了一次。

這便是為什麼volatile不能保證原子性。

volatile的使用場景

根據volatile的特點,保證有序性,可見性,不能保證原子性,因此volatile可以用於那些不需要原子性,或者說原子性已經得到保障的場合:

代碼演示

volatile boolean shutdownRequested 
public void shutdown() {
  shutdownRequested = true;
}
public void work() {
  while(shutdownRequested) {
    //do stuff
 }
}

只要線程對shutdownRequested進行修改,執行work的線程會立即看到,因此會立即停止下來,如果不加volatile的話,它每次去工作記憶體中讀取資料一直是個true,一直執行,都不知道別人已經讓它停了。

程式碼示範:

package com.github.excellent;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 启动线程会被阻塞,flag 从内存读入,会存入寄存器中,下次直接从寄存器取值
 * 因此值一直是false
 * 即使别的线程已经将值更改了,它也不知道
 * 加volatile即可。也可以加锁,只要保证内存可见性即可
 * @auther plg
 * @date 2019/5/2 22:40
 */
public class Testvolatile {
    public static  boolean flag = false;
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            for(;;) {
                System.out.println(flag);
            }
        });
        Thread thread2 = new Thread(()->{
            for(;;){
                flag = true;
            }
        });
        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }
}

執行結果:

深入理解volatile關鍵字

就是這麼笨,別人修改了自己不知道,還輸出false。加一個volatile就ok了。

以上是深入理解volatile關鍵字的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除