日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java核心知识体系7:线程安全性讨论

發布時間:2023/11/29 java 33 coder
生活随笔 收集整理的這篇文章主要介紹了 Java核心知识体系7:线程安全性讨论 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java核心知識體系1:泛型機制詳解
Java核心知識體系2:注解機制詳解
Java核心知識體系3:異常機制詳解
Java核心知識體系4:AOP原理和切面應用
Java核心知識體系5:反射機制詳解
Java核心知識體系6:集合框架詳解

1 為什么需要多線程

我們都知道,CPU、內存、I/O 設備的速度是有極大差異的,為了合理利用 CPU 的高性能,平衡這三者的速度差異,計算機體系結構、操作系統、編譯程序都做出了優化,主要體現為:

  • CPU增加了緩存,均衡了與內存之間的速度差異,但會導致可見性問題
  • 操作系統增加了進程、線程,以分時復用 CPU,進而均衡 CPU 與 I/O 設備的速度差異,但會導致原子性問題
  • 編譯程序優化指令執行次序,使得緩存能夠得到更加合理地利用,但會導致有序性問題

從上面可以看到,雖然多線程平衡了CPU、內存、I/O 設備之間的效率,但是同樣也帶來了一些問題。

2 線程不安全案例分析

如果有多個線程,對一個共享數據進行操作,但沒有采取同步的話,那操作結果可能超出預想,產生不一致。
下面舉個粒子,設置一個計數器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 不一樣

3 并發出現問題的原因

可以看到,上述代碼輸出的結果跟預期的 1000 不一樣,我們需要理清楚發生了什么問題?
★ 并發三要素:可見性、原子性、有序性

3.1 可見性:由CPU緩存引起

CPU緩存是一種高速緩存,用于存儲CPU最近使用的數據。由于CPU緩存比主存儲器更快,因此CPU會盡可能地使用緩存,以提高程序的性能。但是,這也會導致可見性問題。
可見性問題是指當一個線程修改了一個共享變量的值時,另一個線程可能無法立即看到這個修改。

我們舉個簡單的例子,看下面這段代碼:

// 主存中 index 的值默認為 10
System.out.println("主存中的值:" + index);

// Thread1 執行賦值
index = 100;
 
// Thread2 執行的
threadA = index;

因為Thread1修改后的值可能仍然存儲在CPU緩存中,而沒有被寫回主存儲器。這種情況下,Thread2無法讀取到修改后的值,所以導致錯誤信息。
具體來說,當多個線程同時運行在同一個處理器上時,它們共享該處理器的緩存。如果一個線程修改了某個共享變量的值,該值可能被存儲在處理器緩存中,并且未被立即寫回到主存儲器中。
因此,當另一個線程試圖讀取該變量的值時,它可能會從主存儲器中讀取舊的值 10,而不是從處理器緩存中讀取已更新的值 100。

3.2 原子性: 由分時復用引起

原子性:原子性是指一個操作在執行過程中不可分割,即該操作要么完全執行,要么完全不執行。

我們舉個簡單的例子,看下面這段代碼:


// 主存中 index 的值默認為 10
System.out.println("主存中的值:" + index);

// Thread1 執行增值
index += 1;
 
// Thread2 執行增值
index += 1

以上的信息可以看出:

  • 主存的值為10
  • i += 1 這個操作實際執行三條 CPU 指令
    • 變量 i 從內存讀取到 CPU寄存器;
    • 在CPU寄存器中執行 i + 1 操作;
    • 將最后的結果i寫入內存,因為有緩存機制,所以最終可能寫入的是 CPU 緩存而不是內存。
  • 由于CPU分時復用(線程切換)的存在,Thread1執行了第一條指令后,就切換到Thread2執行,Thread2全部執行完成之后,再切換會Thread1執行后續兩條指令,將造成最后寫到內存中的index值是11而不是12。

3.3 有序性: 重排序引起

有序性:即程序執行的順序按照代碼的先后順序執行。

重排序(Reordering)是指在計算機系統中,由于處理器優化或編譯器優化等原因,導致指令執行的順序與程序代碼中的順序不一致。重排序可能會引起有序性錯誤,即在并發或多線程環境中,程序執行的順序與代碼的先后順序不一致,導致程序結果不正確或出現意外的結果。

我們舉個簡單的例子,看下面這段代碼:

int idx = 10;
boolean isCheck = true;
idx += 1;                // 執行語句1  
isCheck = false;          // 執行語句2

上面代碼定義了一個int型變量,定義了一個boolean類型變量,然后分別對兩個變量進行操作。
從代碼順序上看,執行語句1是在執行語句2前面的,那么JVM在真正執行這段代碼的時候會保證語句1一定會在語句2前面執行嗎? 不一定,為什么呢? 這里可能會發生指令重排序(Instruction Reorder)。

重排序(Reordering)是指在計算機系統中,由于處理器優化或編譯器優化等原因,導致指令執行的順序與程序代碼中的順序不一致。重排序可能會引起有序性錯誤,即在并發或多線程環境中,程序執行的順序與代碼的先后順序不一致,導致程序結果不正確或出現意外的結果。

重排序引起的有序性錯誤主要有以下幾種情況:

  1. 指令重排序:處理器為了優化程序的執行,可能會對指令進行重排序。這種重排序不會改變單線程程序的執行結果,但可能會影響多線程程序的行為。例如,一個線程修改了一個共享變量的值,但由于指令重排序,另一個線程在讀取該變量時可能讀取到過時的值。
  2. 內存訪問重排序:處理器為了提高程序的執行效率,可能會對內存訪問進行重排序。例如,一個線程先讀取一個共享變量的值,然后再寫入該值,但由于內存訪問重排序,處理器可能會先執行寫入操作,再執行讀取操作,從而導致其他線程無法正確地讀取到修改后的值。
  3. 同步操作重排序:在并發或多線程環境中,同步操作可能會被重排序。例如,一個線程先釋放了一個鎖,然后再執行另一個操作,但由于同步操作重排序,釋放鎖的操作可能會先于另一個操作執行,從而導致其他線程無法正確地獲取鎖。

為了避免重排序引起的有序性錯誤,可以采用一些同步機制來確保程序的執行順序,如內存屏障(Memory barrier,intel 稱為 memory fence)、指令fence等。這些同步機制可以確保指令的執行順序與代碼中的順序一致,避免指令重排序和內存訪問重排序等問題。同時,也可以使用串行化(Serialization)或事務內存(Transactional memory)等技術來保證并發程序的有序性。

4 總結

  • CPU、內存、I/O 設備的速度是有極大差異的,多線程 的實現是為了合理利用 CPU 的高性能,平衡這三者的速度差異
  • 多線程情況下,并發產生問題的三要素:可見性、原子性、有序性
    • 可見性:由CPU緩存引起
    • 原子性: 由分時復用引起
    • 有序性: 重排序引起

總結

以上是生活随笔為你收集整理的Java核心知识体系7:线程安全性讨论的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。