1. 可見性
可見性:一個線程對共享變量值的修改,能夠及時地被其他線程看到。
共享變量:如果一個變量在多個線程的工作內存中都存在副本,那麼這個變量就是這幾個線程的共享變量。
2. Java內存模型(JVM)
(1)Java內存模型(Java Memory Model)描述了java程序中各種變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取出變量這樣的底層細節。
(2)所有的變量都存儲在主內存中。
(3)每個線程都有自己獨立的工作內存,裏面保存該線程使用到的變量的副本(主內存中該變量的一份拷貝)。
java內存模型兩條規定
(1)線程對共享變量的所有操作都必須在自己的工作內存中進行,不能直接從主內存中讀寫。
(2)不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成。
共享變量可見性實現的原理
線程1對共享變量的修改要想被線程2及時看到,必須要經過如下2個步驟:
(1)把工作內存1中更新過的共享變量刷新到主內存中。
(2)將主內存中最新的共享變量的值更新到工作內存2中。
3 synchronized實現可見性
3.1 可見性
要實現共享變量的可見性,必須保證兩點:
(1)線程修改後的共享變量值能夠及時從工作內存刷新到主內存中。
(2)其他線程能夠及時把共享變量的最新值從主內存更新到自己的工作內存中。
3.2 Java語言層面支持的可見性實現方式:
(1)synchronized
(2)volatile
3.3 synchronized實現可見性
synchronized能夠實現:原子性(同步)、可見性。
JMM關於synchronized的兩條規定:
(1)線程解鎖錢,必須把共享變量的最新值刷新到主內存中。
(2)線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值(注意:加鎖與解鎖需要是同一把鎖)。
線程解鎖前對共享變量的修改在下次加鎖時對其他線程可見。
線程執行互斥代碼的過程:
(1)獲得互斥鎖
(2)清空工作內存
(3)從主內存拷貝變量的最新副本到工作內存
(4)執行代碼
(5)將更改後的共享變量的值刷新到主內存。
(6)釋放互斥鎖
重排序:代碼書寫的順序與實際執行的順序不同,指令重排序是編譯器或處理器爲了提高程序性能而做的優化。
(1)編譯器優化的重排序(編譯器優化)
(2)指令級並行重排序(處理器優化)
(3)內存系統的重排序(處理器優化)
as-if-serial:無論如何重排序,程序執行的結果應該與代碼順序執行的結果一致(Java編譯器、運行時和處理器都會保證Java在單線程下遵循as-if-serial語義)
3.4 導致共享變量在線程間不可見的原因: synchronized解決方案:
(1)線程的交叉執行 --->原子性
(2)重排序結合線程交叉執行 --->原子性
(3)共享變量更新後的值沒有在工作內存與主內存間及時更新。-->可見性
4 volatile實現可見性
volatile如何實現內存可見性:
深入來說:通過加入內存屏障和禁止重排序優化來實現的。
對volatile變量執行寫操作時,會在寫操作後加入一條store屏障指令。
對volatile變量執行讀操作時,會在讀操作前加入一條load屏障指令。
線程讀volatile變量的過程:
(1)從主內存中讀取volatile變量的最新值到線程的工作內存中。
(2)從工作內存中讀取volatile變量的副本。
4.1 保證number自增操作的原子性:
(1)使用synchronized關鍵字
(2)使用ReentrantLock(java.until.concurrent.locks包下)
(3)使用AtomicInterger(vava.util.concurrent.atomic包下)
4.2 volatile適用場合
要在多線程中安全的使用volatile變量,必須同時滿足:
(1)對變量的寫入操作不依賴其當前值
*不滿足:number++、count = count * 5等
*滿足:boolean變量、記錄溫度變化等變量等。
(2)該變量沒有包含在具有其他變量的不變式中。
*不滿足:不變式low < up
4.3 synchronized和volatile的比較
(1)volatile不需要加鎖,比synchronized更輕量級,不會阻塞線程;
(2)從內存可見性角度看,volatile讀相當於加鎖,volatile寫相當於解鎖。
(3)synchronized既能保證可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性。