溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java volatile關(guān)鍵字作用及使用場景詳解

發(fā)布時間:2020-10-10 09:22:41 來源:腳本之家 閱讀:149 作者:孜然狼 欄目:編程語言

1. volatile關(guān)鍵字的作用:保證了變量的可見性(visibility)。被volatile關(guān)鍵字修飾的變量,如果值發(fā)生了變更,其他線程立馬可見,避免出現(xiàn)臟讀的現(xiàn)象。如以下代碼片段,isShutDown被置為true后,doWork方法仍有執(zhí)行。如用volatile修飾isShutDown變量,可避免此問題。

public class VolatileTest3 {
 static class Work {
 boolean isShutDown = false;

 void shutdown() {
  isShutDown = true;
  System.out.println("shutdown!");
 }

 void doWork() {
  while (!isShutDown) {
  System.out.println("doWork");
  }
 }
 }

 public static void main(String[] args) {
 Work work = new Work();

 new Thread(work::doWork).start();
 new Thread(work::doWork).start();
 new Thread(work::doWork).start();
 new Thread(work::shutdown).start();
 new Thread(work::doWork).start();
 new Thread(work::doWork).start();
 new Thread(work::doWork).start();
 }
}

出現(xiàn)臟讀時,運行結(jié)果如下:

java volatile關(guān)鍵字作用及使用場景詳解

2. 為什么會出現(xiàn)臟讀?

Java內(nèi)存模型規(guī)定所有的變量都是存在主存當(dāng)中,每個線程都有自己的工作內(nèi)存。線程對變量的所有操作都必須在工作內(nèi)存中進行,而不能直接對主存進行操作。并且每個線程不能訪問其他線程的工作內(nèi)存。變量的值何時從線程的工作內(nèi)存寫回主存,無法確定。

3. happens-before規(guī)則的理解與勘誤

在網(wǎng)上查volatile關(guān)鍵字相關(guān)信息時,多篇博客提到了happens-before原則,個人對此原則的理解是:當(dāng)操作該volatile變量時,所有前序?qū)υ撟兞康牟僮鞫家淹瓿桑ㄈ绮淮嬖谝炎兏磳懟刂鞔娴那闆r),所有后續(xù)對該變量的操作,都未開始。僅此而已。

這里,我認為網(wǎng)上很常見的一個理論對此理解有誤,如下圖。此觀點認為,由于volatile變量flag的happens-before原則,所以A線程2處對其的寫操作一定先于B線程3處對其的讀操作。其實這種觀點是有邏輯缺陷的,如果存在一個C線程,先讀取flag的值,后寫入flag的值,那C線程的執(zhí)行時機是什么呢?如果還有其他D、E線程呢。。。對于這段代碼的正確理解是,只要3處拿到的flag是true,那么a的值一定是1,而不是0.因為volition修飾的變量,處理器不會對其進行重排序,所以1處對a的賦值,一定發(fā)生在2處對flag的賦值之前。如果flag不是volatile變量,那么1處和2處代碼的執(zhí)行順序是無法保證的(處理器的指令重排序),雖然大部分情況1會先于2執(zhí)行。happens-before原則約束的并不是多線程對同一變量的讀和寫操作之間的順序,而是保證讀操作時,前序所有對該變量的寫操作已生效(寫回主存)。

 

java volatile關(guān)鍵字作用及使用場景詳解

 

驗證如下:

public class VolatileTest {
 static class A {
 int a = 0;
 volatile boolean flag = false;

 void writer() {
  a = 1;   //1
  flag = true;  //2
  System.out.println("write");
 }

 void reader() {
  if (flag) {  //3
  int i = a;  //4
  System.out.println("read true");
  System.out.println("i is :" + i);
  } else {
  int i = a;
  System.out.println("read false");
  System.out.println("i is :" + i);
  }
 }

 }

 public static void main(String[] args) {
 A aaa = new A();
 new Thread(() -> aaa.reader()).start();
 new Thread(() -> aaa.writer()).start();
 }
}

運行結(jié)果如下,在寫操作執(zhí)行之前,讀操作已完成

java volatile關(guān)鍵字作用及使用場景詳解

 

 4. volatile關(guān)鍵字使用場景

注意:volatile只能保證變量的可見性,不能保證對volatile變量操作的原子性,見如下代碼:

public class VolatileTest2 {
 static class A {
 volatile int a = 0;
 void increase() {
  a++;
 }
 int getA(){
  return a;
 }
 }

 public static void main(String[] args) {
 A a = new A();

 new Thread(() -> {
  for (int i = 0;i < 1000;i++) {
  a.increase();
  }
  System.out.println(a.getA());
 }).start();
 new Thread(() -> {
  for (int i = 0;i < 2000;i++) {
  a.increase();
  }
  System.out.println(a.getA());
 }).start();
 new Thread(() -> {
  for (int i = 0;i < 3000;i++) {
  a.increase();
  }
  System.out.println(a.getA());
 }).start();
 new Thread(() -> {
  for (int i = 0;i < 4000;i++) {
  a.increase();
  }
  System.out.println(a.getA());
 }).start();
 new Thread(() -> {
  for (int i = 0;i < 5000;i++) {
  a.increase();
  }
  System.out.println(a.getA());
 }).start();
 }
}

運行結(jié)果如下,volatile無法保證a++操作的原子性。

java volatile關(guān)鍵字作用及使用場景詳解

volatile正確的使用方法可參考:https://www.jb51.net/article/166888.htm

以上就是本次介紹知識點的全部內(nèi)容,感謝大家對億速云的支持。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI