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