多線程併發--Java線程安全

本文爲原創,如需轉載請註明來處便可。java

目錄:Java線程安全

互斥同步

  • 定義
  • 具體實現
  • 字節碼指令的具體執行過程
  • 字節碼指令的2個注意點
  • 現代jdk juc包源碼解讀算法

    非阻塞同步

  • 定義
  • 硬件指令集的發展
  • cas操做的底層實現
  • cas的語義漏洞
  • 使用場景分析編程

    無需同步

  • 定義
  • 無需同步的2種方式
  • ThreadLocal類的源碼分析和原理分析
  • 消費序列的架構分析
  • 使用場景解讀api

互斥同步

定義

互斥同步(mutual exclusion & synchronization)是常見的一種併發正確性保障手段。同步是指多個線程併發訪問共享數據時,保證共享數據在同一時刻只能被一個線程(或者是一些,使用信號量的時候)使用。
互斥是實現同步的一種手段,如臨界區,互斥量,信號量都是主要的互斥實現方式。
互斥是因,同步是果。互斥是方法,同步時目的。安全

具體實現

最基本的互斥實現手段就是synchronized關鍵字。synchronized關鍵字通過編譯以後,會在同步塊的先後分別造成monitorenter和monitorexit字節碼指令,這兩個字節碼都須要一個reference類型的參數來指明鎖定和解鎖的對象。若是Java程序中的synchronized明確指定了對象參數,那就是這個對象的reference,若是沒有明確指定,那就根據synchronized修飾的是實例方法仍是類方法,去取對應的對象實例或class對象來做爲鎖對象。架構

字節碼指令的具體執行過程

在執行monitorenter指令時,首先要嘗試獲取對象的鎖。若是這個對象沒有被鎖定,或者當前線程已經擁有了那個對象的鎖,把鎖的計數器加1,相應的,在執行monitorexit時,將計數器減1,當爲0時,鎖就被釋放。若是獲取鎖失敗,那麼當前線程就要阻塞等待,直到對象鎖被另外一個線程釋放。併發

字節碼指令的2個注意點

  1. synchronized同步塊對同一個線程來講是可重入的,不會出現本身把本身鎖死的狀況。
  2. 同步塊在已進入線程執行完以前,會阻塞其餘的線程進入。
  • tip : Java的線程是映射到操做系統的原生線程上的,若是要阻塞或喚醒一個線程,須要操做系統來完成,就須要從用戶態轉換爲核心態,代價是很大的。因此synchronized是Java語言中的重量級操做。

現代jdk juc包源碼解讀

TODO:【探究】探究reentrantlock鎖(能夠和其餘類型的鎖一塊兒總結研究)【源碼級別】
TODO:【探索】深刻探索juc包【源碼級別】
  • ReentrantLock
    • 功能分析:
      • ReentrantLock 和 synchronized 有同樣的可重入特性。代碼寫法上有區別,一個表現爲api層面上的互斥鎖(lock(),unlock()方法配合try/finally語句塊來完成),一個是原生語法層面上的互斥鎖。
      • 相比synchronized,ReentrantLock增長了一些高級特性,如:等待可中斷,可實現公平鎖,鎖能夠綁定多個條件。
        • 等待可中斷:等待可中斷是指當持有鎖的線程長期不釋放鎖的時候,正在等待鎖的線程能夠放棄等待,改成處理其餘事情。
        • 可實現公平鎖:公平鎖是指多個線程在等待同一個鎖時,必須按照鎖的申請順序來一次得到鎖。ReentrantLock默認狀況下時非公平的,能夠經過帶布爾型的構造函數要求使用公平鎖。
        • 能夠綁定多個條件:綁定多個條件是指一個ReentrantLock對象能夠綁定多個condition對象。【使用synchronized若是須要綁定多個對象,就要添加鎖】
    • 源碼解讀【TODO:能夠單開一個po專門分析juc源碼】
    • TIP:虛擬機在將來的性能改進中確定會更加偏向於原生的synchronized,因此仍是提倡synchronized能實現需求的狀況下,優先使用synchronized【待驗證】。

非阻塞同步

定義(與阻塞同步對比,作了哪些優化)

  • 阻塞同步:阻塞同步最主要的問題就是進行線程阻塞和環境帶來的性能問題,所以這種同步也稱爲阻塞同步(blocking synchronization)。從處理問題的方式上看,互斥同步屬於一種悲觀的併發策略。
  • 非阻塞同步:隨着硬件指令集的發展,咱們有了另外一個選擇:基於衝突檢測的樂觀併發策略。通俗的講,就是先進行操做,若是沒有其餘線程爭用貢獻數據,那麼操做就成功了;若是共享數據有爭用,產生了衝突,再採起其餘的補償措施(常見的補償措施,不斷重試,直到成功),這種樂觀的併發的許多實現都不須要把線程掛起,所以這種同步成爲非阻塞同步(Non-blocking synchronization)。

硬件指令集的發展

樂觀併發策略得益於硬件指令集的發展,硬件保證一個從語義上看起來須要屢次操做的行爲只須要一條處理器指令集就能夠完成,以下:函數

cas操做的底層實現

  • cas指令須要3個操做數,分別是內存位置(再Java中能夠簡單的理解爲變量的內存地址,用V表示),舊的預期值(A),和新值(B)。CAS指令執行時,當且僅當V的值符合舊的預期值A時,處理器用B更新A的值,不然不執行更新,但不管是否更新,返回的都是V的舊值。上述操做是一個原子操做。

    TODO:【探索】從硬件指令集的角度探索cas的原子性。
  • CAS在Java中的實現:
    • 在jdk1.5後,Java纔開始使用CAS操做,該操做由sun.misc.Unsafe類裏面的compareandswapint()等方法包裝提供。虛擬機內部對這些方法多了特殊處理,即時編譯以後就是一條平臺相關的處理器CAS指令,沒有方法調用的過程,或者能夠認定是無條件內聯進去的。
      ##
      TIP:Unsafe類不是提供給用戶調用的類(unsafe.getunsafe()的代碼中限制了只有啓動類加載器(Bootstrap classloader)加載的class才能訪問它【TODO:探索源碼】),所以若是不適用反射手段,咱們只能使用java api間接的訪問它。如:juc包中的整個原子類,其中的compareandset()等方法都使用了unsafe的cas操做。
      TODO:探索源碼】。

cas操做的語義漏洞

cas操做不是完美的,存在這樣一個邏輯漏洞,簡稱 「ABA」 問題:

一個線程one從內存位置V中取出A,這時候另外一個線程two也從內存中取出A,而且two進行了一些操做變成了B,而後two又將V位置的數據變成A,這時候線程one進行CAS操做發現內存中仍然是A,而後one操做成功

非阻塞算法使用場景分析

TODO:【探索】cas操做使用場景分析。

無需同步/無同步方案

定義

若是一個方法自己就不涉及共享數據,那就天然無需同步措施來保證正確性。

無需同步的2種方式

  • 可重入代碼:
    ##
    這種代碼也叫作純代碼,能夠在代碼執行的任什麼時候刻中斷它,轉而去執行另一段代碼(包括遞歸調用它自己),而在控制權返回後,原來的程序不會發生任何錯誤。
    ##
    全部的可重入代碼都是線程安全的,可是並不是全部線程安全的代碼都是可重入的。可重入代碼都有一些共同的特徵,如不依賴堆上共享數據和公共的系統資源,用到的狀態量是從參數傳入,不調用非可重入的方法等。
    ##
    能夠經過簡單的原則來判斷是否可重入性:若是一個方法,他的返回結果是能夠預測的,只要輸入了相同的數據,就能夠返回相同的結果,那麼他就能夠知足可重入的要求,固然也就是線程安全的。

  • 線程本地存儲:
    ##
    java.lang.ThreadLocal類能夠實現線程本地存儲的功能。每個線程的Thread對象都有一個ThreadLocalMap對象,這個對象存儲了以 ThreadLocal.threadLocalHashCode爲鍵,以本地線程變量爲值得K-V值對,ThreadLocal對象就是當前線程的ThreadLocalMap訪問入口,每個ThreadLocal對象都包含了獨一無二的threadLocalHashCode值,就能夠找到對應的本地線程變量。
    ##
    大部分使用消費序列的架構模式都會使用線程本地存儲。

ThreadLocal類的源碼分析和原理分析

TODO:【探究】詳細探究ThreadLocal原理及其源碼實現,必要的話單開一個po。

消費序列的架構分析

TODO:【延申】嘗試探究消息服務隊列的線程模型以及消息架構。【參考rocketMQ,KAFKA】

使用場景解讀

TODO:【延申】探索本地線程存儲的使用場景,並解讀。

參考: 《深刻理解Java虛擬機》第二版 《Java併發編程之美》 《rocket mq實戰》

相關文章
相關標籤/搜索