深刻理解volatile關鍵字

  1.volatile與可見性java

  都知道volatile能夠保證可見性,那麼究竟是如何保證的呢?git

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

  A線程將共享變量讀進工做內存中,同時B線程也將共享變量讀進工做內存中。app

  在A線程對共享變量修改後,會當即刷新到主內存,此時B線程的工做內存中的共享變量就會被設置無效,須要從主內存中從新讀取新值。反映到硬件上就是CPU的Cache line 置爲無效狀態。ide

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

  2.volatile與有序性線程

  都知道volatile能夠保證有序性,那麼究竟是如何保證的呢?excel

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

  底層原理排序

  被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 的最終結果。

  

在這裏插入圖片描述


  不多有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,一直執行,都不知作別人已經讓它停了。

  代碼演示:無錫看婦科哪裏好 http://www.xasgfk.cn/

  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就ok了。

相關文章
相關標籤/搜索