一, 什么是可見性?
1,可見性:一個線程對共享變量值的修改,能夠及時的被其他線程看到。
2,什么是共享變量:如果一個變量在多個線程的工作內存中都存在副本,那么這個變量就是這幾個線程的共享變量
二,Java內存模型(JMM)
1,什么是Java內存模型?
它描述了java程序中各種變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取變量的底層細節
2,內存中如何存儲變量?
所有的變量都存儲在主內存中。
每個線程都有自己獨立的工作內存,里面保存該線程使用到的變量的副本(它是主內存中該變量的一份拷貝),如圖:
3,兩條規定
線程對共享變量的所有操作必須在自己的工作內存中進行,不能直接從主內存中讀寫
不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成
4,共享變量可見性實現的原理(可見性是如何實現的?)
線程修改后的共享變量值能夠及時從工作內存刷新到主內存中。其他線程能夠及時把共享變量的最新值從主內存更新到自己的工作內存中。
三,synchronized是如何實現可見性的?
1,JMM對於synchronized有兩條規定:
線程解鎖前,必須把共享變量的最新值刷新到主內存
線程加鎖時,將清空工作內存中共享變量的值,這樣在使用共享變量時就需要從主內存中重新獲取最新的值
2,線程執行互斥代碼(被synchronized修飾的代碼)的過程:
獲得互斥鎖
清空工作內存
從主內存拷貝變量的最新副本到自己工作內存
執行代碼
將更改后的共享變量的值刷新到主內存中
釋放互斥鎖
四,volatile如何實現可見性?
1,簡單的說:
volatile變量在每次被線程訪問時,都強迫從主內存中讀取該變量的值,而當該變量發生變化時,又會強迫線程將最新的值刷新到主內存,這樣不同的線程總能看到該變量的最新值
2,深入來講:
volatile是通過加入內存屏障和禁止重排序優化來實現。
對volatile變量執行寫操作時,會在寫操作后加入一條sotre屏障指令,強制寫入主內存中
對volatile變量執行讀操作事,會在讀操作前加入一條load屏障指令,強制清空緩沖區數據,需要時從主內存中拿,並禁止指令重排序
2,線程寫volatile變量的過程:
改變線程工作內存中volatile變量副本的值
將改變后的副本的值從工作內存中刷新到主內存
3,線程讀volatile變量的過程
從主內存中讀取volatile變量的最新值到線程的工作內存中
從工作內存讀取volatile變量的副本
4,volatile不保證變量復合操作的原子性
比如:
private int number = 0;
number ++;//注意:這一步不是原子操作,加 volatile也無法保證原子性
因為 number++ 在計算機指令級操作要分三步才能完成:
讀取number的值
將number的值加1
寫入最新的number的值
5,如何保證number自增操作的原子性?
使用synchronized關鍵字
使用ReentrantLock
使用AtomicInteger
五,重排序
定義:代碼書寫的順序和實際執行的順序不同,指令重排序是編譯器或處理器為了提高程序性能而做的優化
分類:
編譯器優化的重排序(編譯器優化)
指令級並行重排序(處理器優化)
內存系統的重排序(處理器優化)
2,舉例分析:
int num1 = 1;
int num2 = 2;
int sum = num1 + num2;
在單線程下:1,2,順序可以重排,但第三行不行。所以重排序不會給線程帶來內存可見性問題。(因為在單線程下,Java編譯器和處理器會保證:無論如何重排序,程序執行的結果和代碼順序執行的結果一致)
在多線程下:程序交替執行,重排序可能導致內存可見性問題
六,可見性分析:
導致共享變量在線程間不可見的原因:
程序的交叉執行
重排序結合線程交叉執行
共享變量更新后的值沒有在工作內存和主內存之間及時更新
七,synchronized為什么可以實現線程安全?
代碼塊具有原子性,線程不能交叉執行
給鎖住的代碼塊是單線程執行的,重排序結果不變
synchronized的兩條規定可以保證變量在工作內存和主內存之間及時更新
八,volatile和sychronized比較
volatile不需要加鎖,比synchronized更輕量級,不會阻塞線程,效率高
從內存可見性角度:volatile讀相當於加鎖,寫相當於解鎖
synchronized既能保證可見性,有能保證原子性。volatile只能保證可見性
注意:final也可以保證內存可見性