目錄:
- 不可見性是什麼?
- volatile 可以保證原子性嗎?
- . 重排的示例和作用?
- Happends - before 是什麼?
- volatile與synchronized 區別?
- 參考
1.不可見性是什麼?
1.1不可見性案例
/**
* 多線程修改變量 會出現 修改值之後不可見性
*/
public class VisibilityDemo1 {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
while (true) {
if (thread.isFlag()) {
System.out.println(" 進入 -- ");
}
}
}
}
class MyThread extends Thread {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(1000); // 標記1
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println(" flag = " + flag);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
控制檯會不會打印輸出:" 進入 –" ?,我估計應該有一部分讀者會認爲答應。
實際的輸出如下:
1.2 如何解決這個問題
解決方案1: 去掉 標記1 代碼
解決方案2: private volatile boolean flag = false;
解決方案3(下一篇 synchronized 進行分析 ): 加入 synchronized
while (true) {
synchronized (VisibilityDemo1.class) {
if (thread.isFlag()) {
System.out.println(" 進入 -- ");
}
}
}
1.3 原理分析
分析之前需要知道JMM(JAVA 內存模型) :
1.JAVA 內存模型 描述了java 程序中 各種變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取變量這樣的底層細節。
2. JMM 有以下規定:
1. 所有的共享變量都存儲於主內存。這是所說的變量指的是實例變量和類變量。不包含局部變量,因爲局部變量時線程私有的。因此不存在競爭問題。
2.每一個線程還存在自己的工作內存,線程的工作內存,保留了被線程使用的變量的工作副本。
3. 線程對變量的所有的操作(讀,取) 都必須在工作內存中完成,而不能直接讀寫主內存中的變量。
4. 不同線程之間也不能直接訪問對方工作內存中的變量,線程間變量的值得傳遞需要通過主內存中轉完成
3. 主內存 和 工作內存示意圖
-
開始問題分析
問題發生的路徑:
1. 子線程 thread 從主內存讀取到數據放入其對應的工作內存
2.將 flag 的值 更改爲true , 但是這個時候 flag的值還沒有寫回主內存
3.此時main 方法讀取到了flag的值爲false
4. 當子線程t將 flag的值寫回去後,但是main 函數裏面的while(true) 調用的是系統比較底層
的代碼,速度快,快到沒有時間再去讀取主內存中的值
所以while(true) 讀取到點值一直是false
分析 解決方案2:(重要 重要 重要)
1.1 子線程t 從主內存讀取到數據放入其對應的工作
1.2 將flag的值更改爲true ,但是這個時候flag的值還沒有寫主內存
1.3 此時main方法 讀取到了flag的值爲false
1.4 當子線程t將flag的值寫回去後,失效( 嗅探技術 )其他線程對此變量副本
1.5 再次對flag進行 操作的時候線程會從主內存讀取最新的值,放入到工作線程中
總結: volatile 保證不通線程對共享變量操作的可見性,也就是說一個線程修改了volatile的修飾的變量,當修改寫會內存時,另外一個線程立即看到最新值.
2. volatile 可以保證原子性嗎??
2.1 volatile 可以保證原子性嗎?
答:不可以, volatile是可見但是線程不安全。 // 解決辦法 synchronized 修飾
public static void main(String[] args) {
Runnable target = new MyTarget();
for (int i = 1; i <= 100; i++) {
new Thread(target, " 第" + i + "線程").start();
}
}
}
class MyTarget implements Runnable {
private volatile int count = 0;
@Override
public void run() {
for (int i = 1; i <= 10000; i++) {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
// 輸入結果 基本不可能等於 100*10000
分析一下:
1、從主內存中讀取數據到工作內存
2、對工作內存中的數據進行++ 操作
3、將工作內存中的數據寫回到主內存
也可以從編譯的字節碼看下
描述:
1. 假設此時X的值100,線程A需要對改變量進行自增1的操作,
首先它需要從主內存中讀取變量x的值。由於cpu的切換關係,此時cpu的執行權被切換了B線程。 A線程就需出於就緒狀態。B 線程出於運行狀態
2 線程b也需要從主內存中讀取X變量的值,由於線程A沒有對X值做任何
因此B讀取到點數據還是100
3 線程b工作內存中x 執行了+1操作,但是未刷新之主內存中
4 此時CPU的執行權切換到了A線程上,由於此時線程B沒有將工作內存中的數據刷新到主 內存,因 此A線程工作內存中的變量值還是1-- 沒有失效。
5 線程B將101 寫入到主內存
6 線程A將101 寫入到主內存
雖然計算量2次,但是隻對A進行了1次修改。
3.重排的實例和作用?
-
什麼是重排: 爲了提高性能,編譯器和處理器常常會對既定代碼執行順序進行指令排序.
有三種情況:
1.1 編譯器優化的重排序,編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。
1.2 指令級並行嗯重排序,現代處理器採用了指令級並行技術來將多條指令重疊執行,如果不存在數據依賴性,處理器可以改變 語句對應機器指令的執行順序。
1.3 內存系統的重排序,由於處理器使用的緩存和讀/寫 緩衝區 ,這使得加載和存儲操作看上去可能是在亂序執行的。
重點:重排序不是萬能的。 也會出問題。
其實這個地方還有 重要點 重要點 重要點.
重排序 返回內存地址引用 和 初始化構造方法詳細情況.
Happends - before 是什麼?
通過 happens-before的概念,描述操作之間內存的可見性。
如果一個操作執行的結果需要對另外一個操作可見,那麼這兩個操作之間必須存在happeds-before關係。
兩個操作可以是在一個線程內,也可以在不同線程之間。
一共有六項規則:
1.程序順序規則(單線程規則)
解釋:一個線程中的每個操作,happens-before於該線程中的任意後續操作。
- 同一個線程中前面的所有寫操作對後面的操作可見。
2. 鎖規則(Synchronized, Lock 等)
解釋: 對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。
如果線程1解鎖monitor,接着線程2鎖定了a,那麼線程1解鎖a之前的寫操作都對線程2可見(線程1和線程2可以是同一線程)
3. volatile 變量規則
解釋 對一個鎖的解鎖,happends-before於隨後對這個鎖的加鎖。
如果線程1寫入了 volatile變量v(臨界資源) , 接着線程2鎖定了a,那麼,線程1解鎖a之前的寫操作都對線程2可見(線程1和線程2可以是同一線程)
4: 傳遞性:
解釋: 如果A happens-before B 且 B happens-before C ,那麼 A happens-before C
A -> B , B ->C 且 A-C
5 start()規則
解釋: 如果線程A執行操作ThreadB.start() 啓動線程B,那麼線程A的TheadB.start() 可以看到 A線程的變量。
假定 線程線程A在執行過程中,通過執行ThreadB.start() 來啓動線程B,那麼線程A對共享變量的修改在接下來線程b開始執行前對線程B可見。 注意: 線程B 啓動之後,線程A在對變量修改線程B未必可見。
6 join 規則
解釋: 如果線程A 執行操作ThreadB.join 併成功返回。那麼線程B中的任意操作happens-before於線程A從ThreadB.join()操作成返回
線程t1 寫入的所有變量,在任意其他線程t2調用t1.join,或者t1.isAlive成功返回後,都對t2可見。
demo: https://download.csdn.net/download/IT_peng/12203228
參考:
https://www.bilibili.com/video/av81907443?p=18 (這個視頻 質量挺高)
java併發編程的藝術
https://www.cnblogs.com/54chensongxia/p/11806836.html
https://blog.csdn.net/anjxue/article/details/51038466?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task