1. 程式人生 > 程式設計 >Java多執行緒volatile原理及用法解析

Java多執行緒volatile原理及用法解析

首先volatile有兩大功能:

保證執行緒可見性

禁止指令重排序

1、保證執行緒可見性

首先我們來看這樣一個程式,其中不加volatile關鍵字執行的結果截然不同,加上volatile程式能夠正常結束,不加則程式進入死迴圈;

package com.designmodal.design.juc01;

import java.util.concurrent.TimeUnit;

/**
 * @author D-L
 * @Classname T001_volatile
 * @Version 1.0
 * @Description volatile 保證執行緒的可見性
 * @Date 2020/7/19 17:30
 */
public class T001_volatile {
  //定義一個變數running
  volatile boolean running = true;

  public void m(){
    while(running){
      //TODO 不做任何的處理
      System.out.println("while is running When can I stop -------------");
    }
    System.out.println("method is end ---------------");
  }

  public static void main(String[] args) {
    T001_volatile t001_volatile = new T001_volatile();
    new Thread(t001_volatile::m,"Thread t1").start();

    //停一秒
    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //修改running的值
    t001_volatile.running = false;
  }
}

通過上面的小程式說明volatile是具有保證執行緒之間的可見性的功能的,具體是如何實現的呢?下面給大家解釋一下:

之前在上一篇講synchronized時提到了 堆記憶體是執行緒共享的,而執行緒在工作時有自己的工作記憶體,對於共享變數running來說,執行緒1和執行緒2在執行的時候先把running變數copy到自己工作記憶體,對這個變數的改變都是在自己的工作記憶體中,並不會直接的反映到其他執行緒,如果加了volatile,running變數改變其他執行緒很快就會知道,這就是執行緒的可見性;

Java多執行緒volatile原理及用法解析

這裡用到的是:MESI(CPU快取一致性協議) MESI的主要思想:當CPU寫資料時,如果該變數是共享資料,給其他CPU傳送訊號,使得其他的CPU中的該變數的快取行無效;歸根結底這裡需要藉助硬體來幫助我們。

Java多執行緒volatile原理及用法解析

volatile保證執行緒可見性但是不能代替synchronized:

package com.designmodal.design.juc01;

import java.util.ArrayList;
import java.util.List;

/**
 * @author D-L
 * @Classname VolatileAndSynchronized
 * @Version 1.0
 * @Description synchronized can not be replaced by volatile
 *        volatile 不能代替synchronized
 *        只能保證可見性 不能保證原子性
 *        count++ 不是原子性操作
 * @Date 2020/xx/xx 23:25
 */
public class VolatileAndSynchronized {
  volatile int count = 0;
  public synchronized void m(){
    for (int i = 0; i < 1000; i++) {
      //非原子性操作 彙編指令至少有三條
      count++;
    }
  }

  public static void main(String[] args) {
    VolatileAndSynchronized v = new VolatileAndSynchronized();
    List<Thread> threads = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
      threads.add(new Thread(v::m,"Thread"+ i));
    }
    threads.forEach(o ->o.start());
    threads.forEach(o ->{
      try {
        o.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });
    System.out.println(v.count);
  }
}

2、禁止指令重排序

指令重排序也是和CPU有關係,加了volatile之後,每次寫都會背執行緒看到。CPU原來執行指令時,是按照一步一步順序來執行的,但是CPU為了提高效率它會把指令併發來執行,第一個指令執行到一半的時候第二條指令就可能已經開始執行了,這叫流水線式的執行;為了充分的利用CPU,就要求編譯器把編譯完的原始碼指令,可能會進行一個指令重新排序;這種架構通過實際驗證,很大效率上提高了CPU的使用效率。

下面從一個面試題來討論一下指令重排序:

面試官:你聽過單例模式嗎?

你:當然聽過,不然沒法聊了。

package com.designmodal.design.juc01;

import java.util.concurrent.TimeUnit;

/**
 * @author D-L
 * @Classname T002_volatile
 * @Version 1.0
 * @Description volatile 指令重排序
 * @Date 2020/7/20 00:48
 */
public class T002_volatile {
  //建立私有的 T002_volatile 有人會問這裡的volatile要不要使用,這裡的答案是肯定的
  private static /**volatile*/ volatile T002_volatile INSTANCE;

  public T002_volatile() {}

  public T002_volatile getInstance(){
    //模擬業務程式碼 這裡為了synchronized更加細粒度,所以使用了雙重檢查
   if(INSTANCE == null){
     synchronized (this){
       //雙重檢查
       if(INSTANCE == null){
         //避免執行緒之間的干擾 在這裡睡一秒
         try {
           TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
         //建立例項物件
         INSTANCE = new T002_volatile();
       }
     }
   }
   return INSTANCE;
  }

  /**
   * 建立100個執行緒 呼叫getInstance() 列印hashcode值
   * @param args
   */
  public static void main(String[] args) {
    T002_volatile t001_volatile = new T002_volatile();
    for (int i = 0; i < 100; i++) {
      new Thread(() ->{
        T002_volatile instance = t001_volatile.getInstance();
        System.out.println(instance.hashCode());
      }).start();
    }

  }
}

在上述的程式碼中:INSTANCE = new T002_volatile(); 經過編譯後的指令是分三步的

1、給指令申請記憶體

2、給成員變數初始化

3、把這塊物件的內容賦給INSTANCE

在第二步這裡既然已經有預設值了,第二個執行緒來檢查,發現已經有值了根本就不會進入鎖住的那份程式碼;加了volatile就不會出現指令重排序了,所以在這個時候一定要保證初始化完成之後才會賦值給這個變數,這就是volatile存在的意義。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。