成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Volatile:JVM 我警告你,我的人你別亂動

開發 架構
Volatile 的意思是,易變的,動蕩不定的,反復無常的。Volatile 的作用就是告訴 JVM,被我修飾的變量它非常善變,你要給我盯好了,一旦有風吹草動要立馬通知大家;另外,你不要自作聰明的調整它的位置(為了性能重排序),它可是說翻臉就翻臉的主兒

Volatile 算是一個面試中的高頻問題了。我們都知道 Volatile 有兩個作用:

  1. 禁止指令重排
  2. 保證內存可見

指令重排序

指令重排序的問題,基本上都是通過 DCL 問題來考察。

DCL,Double Check Look

面試中通常會是下面這種情景:

面試官:用過單例嗎?

你:用過。

面試官:如何實現一個線程安全的懶漢式單例

你:DCL。

面試官:DCL 可以保證線程絕對安全嗎?

你:加 Volatile。

面試官滿意的點點頭。通常情況下,面試中這個問題聊到這里也就結束了。

但這個問題,還有一些可挖掘的內容。我們順著單例的代碼繼續往下挖:

public class Singleton {        private static volatile Singleton instance = null;        private Singleton() {    }        public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}

如果不加 Volatile,會有什么問題呢?問題就出現在下面這行代碼:

instance = new Singleton();

上面這行代碼看起來也平平無奇呀,就是一個賦值操作,還能整什么幺蛾子呢?我們只寫了一行代碼,但 JVM 則需要做好幾步操作。那 JVM 究竟干了啥呢?大概也許可能差不多就是把大象給放冰箱里了。

Java 代碼中的一條賦值語句,到了 JVM 指令層面大概分三步:

  1. 分配一塊內存空間
  2. 初始化
  3. 返回內存地址

下面通過字節碼來一探究竟,為了簡化問題,我們替換成下面的代碼:

Object o = new Object();

編譯以后,通過 javap -v 命令,或者 IDEA 中的 JClassLib 插件可以看到如下圖所示的內容:

通過上面的字節碼信息,可以更加清楚的看到上面提到的那三個步驟:

  1. new 用來分配一塊內存空間
  2. invokspecial 調用了 Object 的 init() 方法,做了初始化
  3. astore_1 就是將 o 指向了 Object 實例對象的內存地址,完成賦值

dup 指令會做一些入棧操作,跟我們要討論的問題關系不大,這里可以先忽略。

到這里,問題就比較明了了。重排的問題會發生在第 2 和 3 步。因為先初始化還是先把對象的內存地址賦值給 o,并沒有必然的前后制約關系。因此,這類的指令在某些情況下會被重排序。

單線程下,這種重排序完全沒有問題。但是多線程的場景下,就有可能出問題:A 線程進入到 instance = new Singleton(); 后,由于指令重排,在 init 之前,將地址給了 o。此時 B 線程來了,發現 instance 不為 null,于是直接拿去用了,然而此時 instance 并沒有初始化,只是個半成品。所以,當 B 拿到 instance 進行操作的時候就會出現問題了。

因此,instance 需要使用 volatile 來修飾,從而禁止進行指令重排。

到這里,你可能要說了,我用單例不加 volatile,這么長時間了也沒遇到你說的重排序問題。你怎么證明「重排序」的存在呢?好問題,下面咱們通過一個小例子來驗證一下重排序是否真的存在。

private static int x = 0;private static int y = 0;private static int a = 0;private static int b = 0;public static void main(String[] args) throws InterruptedException {    int i = 0;    while (true) {        i++;        x = 0; y = 0;        a = 0; b = 0;                Thread one = new Thread(() -> {            a = 1;            x = b;        });        Thread two = new Thread(() -> {            b = 1;            y = a;        });                one.start();        two.start();        one.join();        two.join();        if(x == 0 && y == 0) {            log.info("第 {} 次,x = {}, y = {}", i, x, y);            break;        }    }}

代碼很簡單,就是幾個賦值操作,但卻很巧妙。x、y、a、b 初始都為 0,兩個線程分別給 a、x 和 b、y 賦值,線程 one 先讓 a = 1,然后再讓 x = b;two 線程先讓 b = 1,然后再讓 y = a。

假如不發生重排序,那么以上程序只會有下面六種可能:

每一列,從上到下代表代碼執行的順序。

也就是說,在沒有重排序的情況下,不可能出現 x、y 同時為 0 的情況。而如果 x、y 同時為 0 了,那么一定是出現了下面六種情況中的一種,既發生了重排。

每一列,從上到下代表代碼執行的順序。

運行程序,經過漫長的等待,得到了如下的輸出:

可以看到,在執行了五十多萬次以后,我們終于捕捉到了一次重排序。發生這種情況的幾率很低,所以你就算沒有用 volatile 大概率不會有問題,但我們在今后還是要合理的使用 volatile。

內存可見性

聊完指令重排,接下來聊聊內存可見。這次我們直接上代碼:

private static boolean flag = true;private static void justRun() {    System.out.println("Thread One Start");    while (flag) {}    System.out.println("Thread One End");}public static void main(String[] args) throws InterruptedException {    new Thread(() -> justRun(), "Thread One").start();    TimeUnit.SECONDS.sleep(1);    flag = false;}

代碼很簡單,主線程內開啟一個子線程,子線程中一個 while 循環,當 flag 為 false 時,結束循環。flag 初始值為 true,一秒鐘后,被主線程設置為 false。

按照上面這個邏輯,子線程應該會在程序啟動一秒后停止。然而,當你運行程序后會發現,這個程序就像吃了炫邁一樣,根本停不下來。

這說明主線程對 flag 的修改,子線程并沒有感知到。我們修改一下程序:

private static volatile boolean flag = true;

為 flag 加上 volatile 修飾符,再次運行,你會發現程序運行后,很快(大概一秒鐘)就停止了。這是為啥?是炫邁的藥勁兒過了嗎?

哈哈,當然不是。為了更好的性能,線程都有自己的緩存(CPU 中的高速緩存),我們稱之為工作內存或者本地內存。還有一塊公共內存,我們叫它主從吧。它們的結構大致如下圖所示:

主存中定義了一個 flag 變量,每個線程讀取它的時候,為了更好的性能會在線程本地緩存一份它的副本。讀取的時候也是優先讀取本地副本的值。當 flag 被 volatile 修飾后,每次被修改,都會讓其他線程中的副本失效,從而必須去主存中讀取最新的值。所以,在使用了 volatile 后,子線程能夠立即感知到 flag 的變化,從而停止。

上圖簡化了線程(CPU)的緩存結構,其完整結構如下圖所示:

現代 CPU 共有三級緩存,分別為:L1、L2 和 L3。CPU 中的每個核心都有自己的 L1 和 L2,而一顆 CPU 中的多個核心會共享 L3。

總結

Volatile 的意思是,易變的,動蕩不定的,反復無常的。volatile 的作用就是告訴 JVM,被我修飾的變量它非常善變,你要給我盯好了,一旦有風吹草動要立馬通知大家;另外,你不要自作聰明的調整它的位置(為了性能重排序),它可是說翻臉就翻臉的主兒。

最后,留一個小問題:內存可見性的那個程序中,就算 flag 沒有被 volatile 修飾,線程頂多不是第一時間讀到 flag 的修改,但也不應該一直讀不到呀,這是為啥?這太反直覺了!

責任編輯:姜華 來源: 今日頭條
相關推薦

2022-08-19 08:17:36

JWT服務器身份信息

2020-12-30 09:18:46

JVM內部信息

2019-08-02 17:48:16

戴爾

2022-02-15 20:08:41

JDKJavaWindows

2010-05-14 11:37:46

網絡攻擊黑客美國

2019-01-07 08:59:01

uCPEvCPE網絡

2011-02-23 10:45:51

IT人才

2010-06-03 15:30:01

Windows2008

2020-02-04 16:37:17

k8s 相關應用

2012-08-15 10:50:51

IE6

2023-11-18 09:17:56

Optional代碼

2015-10-28 17:35:35

自動化運維Ansible配置管理

2018-04-06 09:42:39

Windows操作系統功能

2022-12-01 17:17:09

React開發

2009-07-31 19:51:47

云計算

2022-03-15 09:58:12

單例模式系統

2022-09-13 11:50:21

Linux運維命令行

2015-04-16 13:41:24

2022-04-29 08:00:36

web3區塊鏈比特幣
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 在线视频日韩 | 欧美日韩亚洲系列 | 一级毛片黄片 | 亚洲欧美视频 | 久久精品视频免费观看 | 羞羞视频免费观 | 国产欧美精品一区二区 | 国产小视频在线看 | 亚洲视频在线一区 | 九九久久免费视频 | 黑人巨大精品欧美一区二区免费 | 国产一级毛片视频 | 亚洲狠狠| 精品不卡| 国产视频久久 | 久久国产精品一区二区 | 一区二区三区不卡视频 | 国产欧美精品一区二区三区 | 久久精品这里精品 | 一区二区三区在线电影 | 福利社午夜影院 | 99热播精品 | 91网视频| 中文字字幕在线中文乱码范文 | 免费av一区二区三区 | 蜜桃一区二区三区在线 | 亚洲精品国产成人 | 国产精品久久久久久久久久妞妞 | 色在线免费 | 农夫在线精品视频免费观看 | 国产精品一二三区 | 久久久久久久久久久久91 | 精品国产一区二区三区久久久四川 | 99色综合 | 免费久久99精品国产婷婷六月 | 综合久久综合久久 | 久久久久久综合 | 青娱乐一区二区 | 中文字幕 在线观看 | 亚洲乱码一区二区三区在线观看 | 国产一区二区三区免费 |