執行緒安全問題的產生條件、解決方式

語言: CN / TW / HK

1、執行緒安全的產生條件

■ 執行緒安全問題概念:

多個執行緒在 併發 下執行,對 共享資料 進行訪問,造成執行結果 不一致 的情況。

  • 執行緒安全產生前提: 存在多個執行緒 併發執行 (執行緒之間處於爭搶資源的競爭狀態)、 共享資料
  • 執行緒不安全造成的結果: 資料不一致

執行緒安全結果:資料一致;執行緒不安全結果:資料不一致

執行緒安全 問題 :就是執行緒不安全導致的問題

■ 併發、並行

  • 併發(多個執行緒操作同一個資源)

    • CPU 一核 ,模擬出多條執行緒, CPU 快速交替實現 ,多個執行緒之間處於競爭關係,爭搶資源
  • 併發(多個人一個起走)

    • CPU 多核 ,多個執行緒可以同時執行,執行緒池

2、解決執行緒安全的方式

解決思路:破解產生的三個條件即可

比如使用單執行緒;對共享資料處理【比如資料型別是不變的、已知的執行緒安全的變數(執行緒安全集合Vector、原子操作類Atomic等)、執行緒私有的變數等】;對併發處理為同步【比如互斥同步、非阻塞同步】

  • 同步是指在多個執行緒併發訪問共享資料時,保證共享資料在同一個時刻只被一條執行緒使用。

■ 常見的執行緒安全的解決方式

(1) 加鎖方式

  • 使用 同步關鍵詞synchronized 或者 lock 的子類可重入鎖ReentrantLock
  • 互斥同步面臨的主要問題是進行 執行緒阻塞和喚醒 所帶來的效能開銷,因此這種同步也被稱為阻塞同步。【種悲觀的併發策略】
  • 高併發場景 ,建議使用 Lock鎖 ,Lock鎖要比使用Synchronize關鍵字在效能上有極大的提高,而且 Lock鎖底層就是通過AQS+CAS機制實現的 ,而CAS 也是解決執行緒安全的另外一種方式。

★ 說說lock 和 synchronized 鎖的區別

  • synchronized 是一個 關鍵字 ,使用C++實現的, 沒辦法控制鎖的開始、鎖結束,也沒辦法中斷執行緒的執行

  • 而 lock 是 java層面的實現 可以獲取鎖的狀態,開啟鎖,釋放鎖,通過設定可以中斷執行緒的執行,更加靈活

  • 是否自動是否鎖:synchronized 會自動是否鎖,而 lock 需要手動呼叫unlock 方法釋放,否則會死迴圈

lock.lock();//其他沒有拿到鎖的執行緒?阻塞 卡著不動
boolean res = lock.tryLock(1000, TimeUnit.MILLISECONDS);//一秒之後如果沒有拿到鎖,就返回false

lock.lockInterruptibly();//中斷方法

(2) 樂觀併發策略

  • CAS 樂觀併發策略,無鎖機制

  • 像執行緒安全的 原子操作類Atomic 底層就是使用 CAS 思想 ,只是落地實現是依賴 Unsafe 的CPU 原語級別的彙編操作

    new AtomicInteger().getAndAdd(1);//獲取到當前值並加1
    // 底層實現
    public final int getAndAdd(int delta) {
       return unsafe.getAndAddInt(this, valueOffset, delta);
    }
    public final int getAndAddInt(Object var1, long var2, int var4) {
       int var5;
       do {
          var5 = this.getIntVolatile(var1, var2);
       } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
       return var5;
    }

    工作中,不建議使用Unsafe 類,類名就提示你了"不安全"!

    • 基於 衝突檢測 的樂觀併發策略,通俗地說就是不管風險, 先進行操作 ,如果沒有其他執行緒爭用共享資料,那操作就直接成功了;如果共享的資料的確被爭用,產生了 衝突 ,那再進行其他的補償措施,最常用的補償措施是不斷地 重試 ,直到出現 沒有競爭的共享資料為止。
    • 使用樂觀併發策略需要“硬體指令集的發展”?因為我們必須要求 操作和衝突檢測這兩個步驟具備原子性 。靠什麼來保證原子性?==> cpu 指令
  • CAS 可能出現的問題:死迴圈、 ABA 問題

    • ABA 問題的解決:帶有 標記的 原子引用類 AtomicStampedReference ==> 類似思想"樂觀鎖,加版本號"

(3) 執行緒私有區域性變數

  • 執行緒本地儲存 ,比如 threadLocal,執行緒私有的區域性變數,避免的共享變數的競爭

(4) 其他方式

  • 使用 volatile ,利用它的可見性,禁止指令重排的特性,但原子性沒法保證。在多執行緒下 沒有嚴格的寫操作衝突同步要求 ,推薦使用。

    常用的場景是: 使用volatile變數控制執行緒的終止

  • 寫是複製--CopyOnWriteArrayList

    CopyOnWriteArrayList是JUC包提供的執行緒安全的List。

3、總結常用的解決執行緒安全方式

  • 加鎖:sync、lock
  • (無鎖)樂觀併發:CAS
  • 原子操作類:atomic
  • (執行緒區域性變數)變數不共享:threadlocal
  • volatile

如果本文對你有幫助的話記得給一樂點個贊哦,感謝!