- 在單線程的環境下,如果向一個變量先寫入一個值,然後在沒有寫干涉的情況下讀取這個變量的值,那這個時候讀取到的這個變量的值應該是之前寫入的那個值。這本來是一個很正常的事情。但是在多線程環境下,讀和寫發生在不同的線程中的時候,可能會出現:讀線程不能及時的讀取到其他線程寫入的最新的值。這就是所謂的可見性
-
1.引出問題
一個測試代碼:
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 創建線程
Thread thread = new Thread(() -> {
int i = 0;
// 判斷靜態變量是否爲true,如果不是則一直執行
while (!stop) {
i++;
System.out.println("rs:" + i);
}
});
// 線程啓動
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 變更靜態變量
stop = true;
}
預計結果:
1.主線程執行,2.新線程啓動,3.主線程睡眠一秒,4.一秒之後子線程while判斷失效結束循環,線程結束。
測試結果:
while一直循環,程序無法停止。
- 嘗試變更代碼得到預計結果
1.添加一條打印語句
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 創建線程
Thread thread = new Thread(() -> {
int i = 0;
// 判斷靜態變量是否爲true,如果不是則一直執行
while (!stop) {
i++;
// 添加一條打印語句
System.out.println("rs:" + i);
}
});
// 線程啓動
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 變更靜態變量
stop = true;
}
執行結果:
程序正常結束。
2.讓子線程睡眠
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 創建線程
Thread thread = new Thread(() -> {
int i = 0;
// 判斷靜態變量是否爲true,如果不是則一直執行
while (!stop) {
i++;
// 讓子線程睡眠
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 線程啓動
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 變更靜態變量
stop = true;
}
執行結果:
程序正常結束
3.對靜態變量添加一個 volatile 屬性
//添加volatile屬性
public volatile static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 創建線程
Thread thread = new Thread(() -> {
int i = 0;
// 判斷靜態變量是否爲true,如果不是則一直執行
while (!stop) {
i++;
}
});
// 線程啓動
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 變更靜態變量
stop = true;
}
運行結果:
程序正常結束。
-
嘗試
- print就可以導致循環結束
是因爲當前安裝的java虛擬機是server版本,server版本內部對代碼做了一個JIT深度優化(活性失敗)。
查詢當前jvm版本,如下圖:
while (!stop) {
i++;
}
//這段代碼等價於
if (!stop) {
while (true) {
i++;
}
}
如果想要jvm不對代碼進行優化可以在啓動參數中添加:-Djava.compiler=NONE
原始代碼測試運行:
程序正常結束。
查看 System.out.print(); 底層代碼,發現調用了
1.IO流
2.synchronized
那我們分別對代碼進行 IO操作,和 加鎖操作(記得刪掉啓動參數中的-Djava.compiler=NONE)
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 創建線程
Thread thread = new Thread(() -> {
int i = 0;
// 判斷靜態變量是否爲true,如果不是則一直執行
while (!stop) {
i++;
synchronized (TestDemoDel.class){
}
}
});
// 線程啓動
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 變更靜態變量
stop = true;
}
public static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
// 創建線程
Thread thread = new Thread(() -> {
int i = 0;
// 判斷靜態變量是否爲true,如果不是則一直執行
while (!stop) {
i++;
new File("tea.txt") ;
}
});
// 線程啓動
thread.start();
// 睡眠一秒
Thread.sleep(1000);
// 變更靜態變量
stop = true;
}
啓動,執行。發現也會結束循環,程序停止。
-
概念
通過對上述代碼查看彙編指令,使用HSDIS工具,具體的使用方法自行百度。
可以看到,使用volatile關鍵字或各種方式使程序停止之後,多了一個Lock指令。
0x00000000037028f3: lock add dword ptr [rsp],0h ;*putstatic stop
0x0000000002b7ddab: push 0ffffffffc4834800h ;*putstatic stop; -
硬件層面
CPU/內存/IO設備
- CPU層面增加了高速緩存
- 操作系統,進程、線程、| CPU時間片來切換
- 編譯器的優化 ,更合理的利用CPU的高速緩存.
看下圖:
高速緩存分爲 L1,L2,L3
- L1d 數據緩存
- L1i 指令緩存
- L2二級緩存
- L3三級緩存
離線程越近,速度越高。
當一個線程不存在某條內容數據時,會去內存中加載,加載之後,會讀取到CPU的高速緩存中,下一次會先從高速緩存中加載,如果緩存存在,那就可以直接使用。大大加快了線程IO性能的開銷,從而提升性能的以及CPU的利用率。
當使用 volatile 關鍵字時,會對緩存加鎖。使其從內存中讀取數據。所以會使其線程可見