您好,登錄后才能下訂單哦!
上一篇,我們談了談如何通過同步來保證共享變量的原子性(一個操作或者多個操作要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行),本篇我們來談一談如何保證共享變量的可見性(多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值)。
我們使用同步的目的不僅是,不希望某個線程在使用對象狀態(tài)時,另外一個線程在修改狀態(tài),這樣容易造成混亂;我們還希望某個線程修改了對象狀態(tài)后,其他線程能夠看到修改后的狀態(tài)——這就涉及到了一個新的名詞:內(nèi)存(可省略)可見性。
要了解可見性,我們得先來了解一下 Java 內(nèi)存模型。
Java 內(nèi)存模型(Java Memory Model,簡稱 JMM)描述了 Java 程序中各種變量(線程之間的共享變量)的訪問規(guī)則,以及在 JVM 中將變量存儲到內(nèi)存→從內(nèi)存中讀取變量的底層細(xì)節(jié)。
要知道,所有的變量都是存儲在主內(nèi)存中的,每個線程會有自己獨立的工作內(nèi)存,里面保存了該線程使用到的變量副本(主內(nèi)存中變量的一個拷貝)。見下圖。
也就是說,線程 1 對共享變量 chenmo 的修改要想被線程 2 及時看到,必須要經(jīng)過 2 個步驟:
1、把工作內(nèi)存 1 中更新過的共享變量刷新到主內(nèi)存中。
2、將主內(nèi)存中最新的共享變量的值更新到工作內(nèi)存 2 中。
那假如共享變量沒有及時被其他線程看到的話,會發(fā)生什么問題呢?
public class Wanger {
private static boolean chenmo = false;
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!chenmo) {
}
}
});
thread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
chenmo = true;
}
}
這段代碼的本意是:在主線程中創(chuàng)建子線程,然后啟動它,當(dāng)主線程休眠 500 毫秒后,把共享變量 chenmo 的值修改為 true 的時候,子線程中的 while 循環(huán)停下來。但運行這段代碼后,程序似乎進(jìn)入了死循環(huán),過了 N 個 500 毫秒,也沒有要停下來的意思。
為什么會這樣呢?
因為主線程對共享變量 chenmo 的修改沒有及時通知到子線程(子線程在運行的時候,會將 chenmo 變量的值拷貝一份放在自己的工作內(nèi)存當(dāng)中),當(dāng)主線程更改了 chenmo 變量的值之后,但是還沒來得及寫入到主存當(dāng)中,那么子線程此時就不知道主線程對 chenmo 變量的更改,因此還會一直循環(huán)下去。
換句話說,就是:普通的共享變量不能保證可見性,因為普通共享變量被修改之后,什么時候被寫入主內(nèi)存是不確定的,當(dāng)其他線程去讀取時,此時內(nèi)存中可能還是原來的舊值,因此無法保證可見性。
那怎么解決這個問題呢?
使用 volatile 關(guān)鍵字修飾共享變量 chenmo。
因為 volatile 變量被線程訪問時,會強迫線程從主內(nèi)存中重讀變量的值,而當(dāng)變量被線程修改時,又會強迫線程將最近的值刷新到主內(nèi)存當(dāng)中。這樣的話,線程在任何時候總能看到變量的最新值。
我們來使用 volatile 修飾一下共享變量 chenmo。
private static volatile boolean chenmo = false;
再次運行代碼后,程序在一瞬間就結(jié)束了,500 毫秒畢竟很短啊。在主線程(main 方法)將 chenmo 修改為 true 后,chenmo 變量的值立即寫入到了主內(nèi)存當(dāng)中;同時,導(dǎo)致子線程的工作內(nèi)存中緩存變量 chenmo 的副本失效了;當(dāng)子線程讀取 chenmo 變量時,發(fā)現(xiàn)自己的緩存副本無效了,就會去主內(nèi)存讀取最新的值(由 false 變?yōu)?true 了),于是 while 循環(huán)也就停止了。
也就是說,在某種場景下,我們可以使用 volatile 關(guān)鍵字來安全地共享變量。這種場景之一就是:狀態(tài)真正獨立于程序內(nèi)地其他內(nèi)容,比如一個布爾狀態(tài)標(biāo)志(從 false 到 true,也可以再轉(zhuǎn)換到 false),用于指示發(fā)生了一個重要的一次性事件。
至于 volatile 的原理和實現(xiàn)機(jī)制,本篇不再深入展開了(小編自己沒搞懂,尷尬而不失禮貌的笑一笑)。
需要再次強調(diào)地是:
volatile 變量可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 相比,volatile 變量運行時地開銷比較少,但是它所能實現(xiàn)的功能也僅是 synchronized 的一部分(只能確??梢娦?,不能確保原子性)。
原子性我們上一篇已經(jīng)討論過了,增量操作(i++)看上去像一個單獨操作,但實際上它是一個由“讀?。薷模瓕懭搿苯M成的序列操作,因此 volatile 并不能為其提供必須的原子特性。
除了 volatile 和 synchronized,Lock 也能夠保證可見性,它能保證同一時刻只有一個線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當(dāng)中。關(guān)于 Lock 的更多細(xì)節(jié),我們后面再進(jìn)行討論。
好了,共享變量的可見性就先介紹到這。希望本篇文章能夠?qū)Υ蠹矣兴鶐椭?,謝謝大家的閱讀。
上一篇:如何保證共享變量的原子性?
下一篇:如何保證對象的線程安全性
微信搜索「*沉默王×××免費視頻**」獲取 500G 高質(zhì)量教學(xué)視頻(已分門別類)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。