Java核心知識體系1:泛型機制詳解
Java核心知識體系2:註解機制詳解
Java核心知識體系3:異常機制詳解
Java核心知識體系4:AOP原理和切面應用
Java核心知識體系5:反射機制詳解
Java核心知識體系6:集合框架詳解
我們都知道,CPU、記憶體、I/O 裝置的速度是有極大差異的,為了合理利用 CPU 的高效能,平衡這三者的速度差異,電腦架構、作業系統、編譯程式都做出了優化,主要體現為:
從上面可以看到,雖然多執行緒平衡了CPU、記憶體、I/O 裝置之間的效率,但是同樣也帶來了一些問題。
如果有多個執行緒,對一個共用資料進行操作,但沒有采取同步的話,那操作結果可能超出預想,產生不一致。
下面舉個粒子,設定一個計數器count,我們通過1000個執行緒同時對它進行增量操作,看看操作之後的值,是不是符合預想中的1000。
public class UnsafeThreadTest {
private int count = 0;
public void add() {
count += 1;
}
public int get() {
return count;
}
}
public static void main(String[] args) throws InterruptedException {
final int threadNum = 1000;
UnsafeThreadTest threadTest = new UnsafeThreadTest();
final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
ExecutorService executorSvc = Executors.newCachedThreadPool();
// 執行並行計數
for (int idx = 0; idx < threadNum; idx ++) {
executorSvc.execute(() -> {
threadTest.add();
countDownLatch.countDown();
});
}
countDownLatch.await();
// 關閉執行緒池
executorSvc.shutdown();
System.out.println("最終計數:" + threadTest.get());
}
最終計數:994 // 結果跟預期的 1000 不一樣
可以看到,上述程式碼輸出的結果跟預期的 1000 不一樣,我們需要理清楚發生了什麼問題?
★ 並行三要素:可見性、原子性、有序性
CPU快取是一種快取記憶體,用於儲存CPU最近使用的資料。由於CPU快取比主記憶體儲器更快,因此CPU會盡可能地使用快取,以提高程式的效能。但是,這也會導致可見性問題。
可見性問題是指當一個執行緒修改了一個共用變數的值時,另一個執行緒可能無法立即看到這個修改。
我們舉個簡單的例子,看下面這段程式碼:
// 主記憶體中 index 的值預設為 10
System.out.println("主記憶體中的值:" + index);
// Thread1 執行賦值
index = 100;
// Thread2 執行的
threadA = index;
因為Thread1修改後的值可能仍然儲存在CPU快取中,而沒有被寫回主記憶體儲器。這種情況下,Thread2無法讀取到修改後的值,所以導致錯誤資訊。
具體來說,當多個執行緒同時執行在同一個處理器上時,它們共用該處理器的快取。如果一個執行緒修改了某個共用變數的值,該值可能被儲存在處理器快取中,並且未被立即寫回到主記憶體儲器中。
因此,當另一個執行緒試圖讀取該變數的值時,它可能會從主記憶體儲器中讀取舊的值 10,而不是從處理器快取中讀取已更新的值 100。
原子性:原子性是指一個操作在執行過程中不可分割,即該操作要麼完全執行,要麼完全不執行。
我們舉個簡單的例子,看下面這段程式碼:
// 主記憶體中 index 的值預設為 10
System.out.println("主記憶體中的值:" + index);
// Thread1 執行增值
index += 1;
// Thread2 執行增值
index += 1
以上的資訊可以看出:
有序性:即程式執行的順序按照程式碼的先後順序執行。
重排序(Reordering)是指在計算機系統中,由於處理器優化或編譯器優化等原因,導致指令執行的順序與程式程式碼中的順序不一致。重排序可能會引起有序性錯誤,即在並行或多執行緒環境中,程式執行的順序與程式碼的先後順序不一致,導致程式結果不正確或出現意外的結果。
我們舉個簡單的例子,看下面這段程式碼:
int idx = 10;
boolean isCheck = true;
idx += 1; // 執行語句1
isCheck = false; // 執行語句2
上面程式碼定義了一個int型變數,定義了一個boolean型別變數,然後分別對兩個變數進行操作。
從程式碼順序上看,執行語句1是在執行語句2前面的,那麼JVM在真正執行這段程式碼的時候會保證語句1一定會在語句2前面執行嗎? 不一定,為什麼呢? 這裡可能會發生指令重排序(Instruction Reorder)。
重排序(Reordering)是指在計算機系統中,由於處理器優化或編譯器優化等原因,導致指令執行的順序與程式程式碼中的順序不一致。重排序可能會引起有序性錯誤,即在並行或多執行緒環境中,程式執行的順序與程式碼的先後順序不一致,導致程式結果不正確或出現意外的結果。
重排序引起的有序性錯誤主要有以下幾種情況:
為了避免重排序引起的有序性錯誤,可以採用一些同步機制來確保程式的執行順序,如記憶體屏障(Memory barrier,intel 稱為 memory fence)、指令fence等。這些同步機制可以確保指令的執行順序與程式碼中的順序一致,避免指令重排序和記憶體存取重排序等問題。同時,也可以使用序列化(Serialization)或事務記憶體(Transactional memory)等技術來保證並行程式的有序性。