1 現象
/**
* @Description: 寫線程更新INIT_VALUE的值,讀線程打印INIT_VALUE更新後的值
* @Auther: zhurongsheng
* @Date: 2020/3/30 12:55
*/
public class VolatileTest {
private volatile static int INIT_VALUE = 0;
private final static int MAX_LIMIT = 50;
public static void main(String[] args) {
/**
* 當INIT_VALUE更新時,打印INIT_VALUE的值
*/
new Thread(() -> {
int localValue = INIT_VALUE;
while (localValue < MAX_LIMIT) {
if (localValue != INIT_VALUE) {
System.out.println("The value updated to " + INIT_VALUE);
localValue=INIT_VALUE;
}
}
}, "READER").start();
/**
* 每隔0.5秒 增加 INIT_VALUE
*/
new Thread(() -> {
int localValue = INIT_VALUE;
while (INIT_VALUE < MAX_LIMIT) {
System.out.println("Update the value to " + ++localValue);
INIT_VALUE=localValue;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "UPDATE").start();
}
}
2 CPU緩存問題
2.1數據不一致問題
UPDATE線程和READER各自玩各自的Cache,所以UPDATE線程更新INIT_VALUE時更新的是CACHE2的數據,READER是讀取不到的
2.2 兩個線程t1,t2同時對INIT_VALUE進行操作,由於各自玩各自的緩存,是否能打印出兩個兩個線程的0-10
/**
* @Description: 兩個線程t1,t2同時對INIT_VALUE進行操作,由於各自玩各自的緩存,是否能打印出兩個兩個線程的0-10
* @Auther: zhurongsheng
* @Date: 2020/3/30 12:55
*/
public class VolatileTest2 {
private static int INIT_VALUE = 0;
private final static int MAX_LIMIT = 10;
public static void main(String[] args) {
new Thread(() -> {
int localValue = INIT_VALUE;
while (INIT_VALUE < MAX_LIMIT) {
System.out.println("t1 the value to " + ++localValue);
INIT_VALUE = localValue;
}
}, "t1").start();
new Thread(() -> {
int localValue = INIT_VALUE;
while (INIT_VALUE < MAX_LIMIT) {
System.out.println("t2 the value to " + ++localValue);
INIT_VALUE = localValue;
}
}, "t2").start();
}
}
2.3 解決方案
(1) 給數據總線加鎖
CPU跟其它硬件通訊方式都是通過總線的,總線又分(數據、地址、控制),當操作main memory數據時,對該部分的數據總線加鎖。這樣能解決問題,但是多核會串行化,導致計算速度慢。
(2)CPU高速緩存一致性協議
當CPU寫入數據的時候,如果發現該變量是共享變量(在其它CPU中也存在該變量的副本),會發出一個信號,通知其他CPU該變量的緩存無效了。
3 併發編程的三個重要概念
3.1 原子性
原子性:即一個操作或者多個操作要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。
3.2 可見性
可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
3.3 有序性
有序性:即程序執行的順序按照代碼的先後順序執行。
(1) 代碼
int i = 0;
boolean flag = false;
//語句1
i = 1;
//語句2
flag = true;
(2) 重排序(最終一致性)
上面代碼定義了一個int型變量,定義了一個boolean類型變量,然後分別對兩個變量進行賦值操作。從代碼順序上看,語句1是在語句2前面的,那麼JVM在真正執行這段代碼的時候會保證語句1一定會在語句2前面執行嗎?不一定,爲什麼呢?這裏可能會發生指令重排序(Instruction Reorder)。重排序,一般來說,處理器爲了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。
5 volatile
5.1 volatile
(1) 保證了不同線程間的可見性,如果是寫操作,強制把緩存的數據寫入主存,使其它CPU緩存中的數據失效
(2) 禁止對其進行重排序,保證有序性
(3) 並未保證原子性
5.2 可見性
/**
* @Description: 可見性測試
* @Auther: zhurongsheng
* @Date: 2020/3/30 12:55
*/
public class VolatileTest1 {
private static volatile int INIT_VALUE = 0;
private final static int MAX_LIMIT = 50;
public static void main(String[] args) {
/**
* 當INIT_VALUE更新時,打印INIT_VALUE的值
*/
new Thread(() -> {
int localValue = INIT_VALUE;
while (localValue < MAX_LIMIT) {
if (localValue != INIT_VALUE) {
System.out.println("The value updated to " + INIT_VALUE);
localValue = INIT_VALUE;
}
}
}, "READER").start();
/**
* 每隔0.5秒 增加 INIT_VALUE
*/
new Thread(() -> {
int localValue = INIT_VALUE;
while (INIT_VALUE < MAX_LIMIT) {
System.out.println("Update the value to " + ++localValue);
INIT_VALUE = localValue;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "UPDATE").start();
}
}
2.3 原子性測試
/**
* @Description: 兩個線程同時對INIT_VALUE進行累加操作看是否是協同操作
* @Auther: zhurongsheng
* @Date: 2020/3/31 22:26
*/
public class VolatileTest3 {
private volatile static int INIT_VALUE = 0;
private final static int MAX_LIMIT = 10;
public static void main(String[] args) {
new Thread(() -> {
int localValue = INIT_VALUE;
while (INIT_VALUE < MAX_LIMIT) {
System.out.println("t1 the value to " + ++localValue);
INIT_VALUE = localValue;
}
}, "t1").start();
new Thread(() -> {
int localValue = INIT_VALUE;
while (INIT_VALUE < MAX_LIMIT) {
System.out.println("t2 the value to " + ++localValue);
INIT_VALUE = localValue;
}
}, "t2").start();
}
}