JAVA併發編程之-Volatile關鍵字及內存可見性

做者:畢來生
微信:878799579

1. 什麼是JUC?

JUC全稱 java.util.concurrent 是在併發編程中很經常使用的實用工具類java

2.Volatile關鍵字

一、若是一個變量被volatile關鍵字修飾,那麼這個變量對全部線程都是可見的。
二、若是某條線程修改了被Volatile修飾的這個變量值,修改後的值對於其餘線程來時是當即可見的。
三、並非通過Volatile修飾過的變量在多線程下就是安全的
四、多線程間可使用SynchronousQueue或者Exchanger進行數據之間傳遞編程

3.內存可見性

內存可見性(Memory Visibility)是指當某個線程正在使用對象狀態 而另外一個線程在同時修改該狀態,須要確保當一個線程修改了對象 狀態後,其餘線程可以看到發生的狀態變化。
可見性錯誤是指當讀操做與寫操做在不一樣的線程中執行時,咱們沒法確保執行讀操做的線程能適時地看到其餘線程寫入的值,有時甚至是根本不可能的事情。
原理同CAS原理相同,不懂的同窗能夠自行百度,附上一張CAS演示圖供你們參考緩存

在這裏插入圖片描述

4.實戰舉例

經過線程來修改變量count的值,使用Volatile關鍵字修飾和不使用Volatile修飾count變量結果對比。安全

首先咱們來看一下經過內部類實現Runnable,變量<font color="red">使用Volatile關鍵字</font>修飾演示以及結果微信

package org.bilaisheng.juc;

/**
 * @Author: bilaisheng
 * @Wechat: 878799579
 * @Date: 2019/1/1 16:29
 * @Todo: 經過內部類實現Runnable,變量使用Volatile關鍵字修飾演示
 * @Version : JDK11 , IDEA2018
 */
public class NoVolatileTest{

    public static void main(String[] args) {
        NoVolatileThread noVolatileThread = new NoVolatileThread();
        new Thread(noVolatileThread).start();

        while (true){
            if(noVolatileThread.isFlag()){
                System.out.println("flag 此時爲true !");
                break;
            }
        }
    }
}

class NoVolatileThread implements Runnable{

    private boolean flag = false;
    
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        flag = true;

        System.out.println(Thread.currentThread().getName() + " flag = " + flag);
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

運行結果以下圖所示:多線程

在這裏插入圖片描述


接下來咱們來看一下經過內部類實現Runnable,變量<font color="red">不使用Volatile關鍵字</font>修飾演示以及結果併發

package org.bilaisheng.juc;

/**
 * @Author: bilaisheng
 * @Wechat: 878799579
 * @Date: 2019/1/1 16:53
 * @Todo: 經過內部類實現Runnable,變量使用Volatile關鍵字修飾演示
 * @Version : JDK11 , IDEA2018
 */
public class VolatileTest{

    public static void main(String[] args) {
        VolatileThread volatileThread = new VolatileThread();
        new Thread(volatileThread).start();

        while (true){
            // if的判斷volatile保證當時確實正確,而後線程a可能處於休眠狀態,
            // 線程b也判斷不存在,b線程就new了一個。
            // 而後a線程wake up,據需執行new volatile獲取最新值。
            if(volatileThread.isFlag()){
                System.out.println("flag 此時爲true !");
                break;
            }
        }
    }
}

class VolatileThread implements Runnable{

    private volatile boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;

        System.out.println(Thread.currentThread().getName() + " flag = " + flag);
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

運行結果以下圖所示:
在這裏插入圖片描述ide

經過對比咱們發如今經過Volatile修飾和不經過Volatile修飾的變量,輸出結果居然會有些誤差。究竟是爲何呢?高併發

咱們逐步拆解上面代碼執行步驟:工具

一、針對於不使用Volatile關鍵字修飾變量:

  • 步驟一:默認flag = false;
  • 步驟二main線程的緩存區域沒有刷新 flag的值。因此flag 仍是false。故沒有輸出<flag 此時爲true !>
  • 步驟三:子線程輸出 Thread-0 flag = true

二、針對於使用Volatile關鍵字修飾變量:

  • 步驟一:默認flag = false;
  • 步驟二:主線程看到flag是被Volatile關鍵字修飾的變量。則獲取最新的flag變量值,此時flag = true。故輸出<flag 此時爲true !>
  • 步驟三:子線程輸出 Thread-0 flag = true

5. Volatile的優勢

可見性:被Volatile修飾的變量能夠立刻刷新主內存中的值,保證其餘線程在獲取時能夠獲取最新值,全部線程看到該變量的值均相同。

輕量級的synchronized,高併發下保證變量的可見性。


6.Volatile的缺點

一、頻繁刷新主內存中變量,可能會形成性能瓶頸

二、不具有操做的原子性,不適合在對該變量的寫操做依賴於變量自己本身。例如i++,並不能經過volatile來保證原子性

7.喜歡就關注我吧

在這裏插入圖片描述

相關文章
相關標籤/搜索