天天看點

并發程式設計 Java并發機制的底層實作原理

volatile原理

volatile是輕量級的synchronized,在多處理器開發中保證了共享變量的"可見性",volatile是一個輕量級的synchronized,在多CPU開發中保證了共享變量的“可見性”,也就是說當一個線程修改一個共享變量的時候,另一個線程能夠讀取到所修改的值。如果volatile使用恰當的話,它将比synchronized的使用和執行成本更低,不會引起上下文的切換和排程。
如果一個變量被聲明為volatile,Java線程記憶體模型確定所有線程看到這個變量的值是一緻的。 volatile修飾的共享變量在轉換為彙編語言後,會出現Lock字首指令,該指令在多核處理器下引發了兩件事:
1、将目前處理器緩存行(CPU cache中可以配置設定的最小存儲機關)的資料寫回到系統記憶體。
2、這個寫回記憶體的操作使得其他CPU裡緩存了該記憶體位址的資料無效。    為了提高處理速度,CPU不直接和記憶體通信,而是将記憶體資料讀取到cache後進行操作,但何時寫回到記憶體是不确定的。如果線程volatile變量進行了寫操作,則JVM會向CPU發送一條Lock字首指令,将該變量的所在的cache行的資料寫回到記憶體中。同時,為了保證其他CPU所讀取到的cache值是一緻的,就戶實作cache一緻性協定,每個CPU通過嗅探在總線上傳播的資料來檢查自己所緩存的值是否過期。如果CPU發現自己cache行中所對應的記憶體位址被修改,就會将該cache行設定為無效,進而在對該資料進行修改的時候重新從記憶體中讀取。
volatile的兩條實作原則:
    1、Lock字首指令會引起CPU cache(處理器緩存)寫回到記憶體。
    2、一個CPU的cache(緩存)寫回到記憶體會導緻其他處理器緩存無效。      

如何優化volatile

用一種追加位元組的方式來優化,追加位元組填滿緩沖行(Linked-TransferQueue),大多處理器告訴緩存行是64位元組。試圖修改緩存行資料時,會鎖定緩存行。導緻其他處理器不能通路鎖定的節點。
    什麼時候不能追加到64位元組
        緩存行為32位元組的cpu時
        共享變量不會被頻繁寫入
        ps:java7會淘汰或重新排列無用字段,不能單獨使用Object對象來填充      

synchronized原理

**synchronized具體表現為三種狀況:**
   對于普通同步方法,鎖是目前執行個體對象
   對于靜态同步方法,鎖是目前類的Class對象
   對于同步方法塊,鎖是synchronized括号裡面配置的對象

   **synchronized 實作原理:**
      JVM基于進入和退出Monitor對象來實作方法同步和代碼塊同步,但兩者實作細節不同。
      代碼塊同步:使用monitorenter 和 monitorexit 指令實作。monitorenter指令在編譯後插入到同步代碼塊的開始位置,monitorexit指令插入到方法結束和異常處。
      方法同步:依靠的是方法修飾符上的 ACC_SYNCHRONIZED 實作。(網上資料)

   **Java對象頭:**
   synchronized 用的鎖是存在Java對象頭裡的。
   鎖的更新與對比:
      鎖的四種狀态:無鎖狀态、偏向鎖狀态、輕量級鎖狀态和重量級鎖狀态。
      鎖可以更新但不能降級,目的是為了提高獲得鎖和釋放鎖的效率。(如何提高的?)

   **偏向鎖:**
      引入原因:大多數情況下,不僅不存在多線程競争,而且總是由同一線程多次獲得,為了讓獲得鎖的代價更低而引入偏向鎖。
      實作方式:當一個線程通路同步代碼塊并擷取鎖時,會在對象頭和棧幀中的鎖記錄裡存儲偏向鎖的線程ID,以後該線程進入和退出同步代碼塊時不需要進行CAS操作來加鎖和解鎖,隻需簡單測試一下對象頭的MarkWord裡是否存儲指向目前線程的偏向鎖。

   **偏向鎖的進入:**
      判斷是否存在指向目前線程的偏向鎖:
      存在,直接進入同步代碼塊。(表示目前線程持有鎖)
      不存在,且偏向鎖辨別為0,使用CAS競争鎖。(目前為無鎖狀态)
不存在,且偏向鎖辨別為1,嘗試使用CAS将對象頭的偏向鎖指向目前線程。(其他線程持有鎖,觸發持有鎖的線程執行偏向鎖撤銷操作)

   **偏向鎖的撤銷:**
      偏向鎖使用了一種等到競争出現才釋放鎖的機制,是以當其他線程嘗試競争偏向鎖時,持有偏向鎖的線程才會釋放鎖。
      偏向鎖的撤銷需要等待全局安全點(在這個時間點上沒有正在執行的代碼)。

   **輕量級鎖:**
      輕量級鎖加鎖
      1、在目前線程棧幀中建立存儲鎖記錄的空間,并将對象頭的Mark Word 複制到鎖記錄中。
      2、線程嘗試使用CAS将對象頭中的Mark Word 替換為指向鎖記錄的指針。
         如果成功:目前線程獲得鎖。
         如果失敗:嘗試使用自旋擷取鎖。

   **輕量級鎖解鎖**
      輕量級鎖解鎖時,使用CAS操作将Displaced Mark Word 替換到對象頭。
         如果成功:表示沒有競争。
         如果失敗:表示目前存在競争,鎖膨脹為重量級鎖。      

鎖的優缺點對比

鎖 優點 缺點 适用場景

偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距 如果線程間存在鎖競争,會帶來額外的鎖撤銷的消耗 适用于隻有一個線程通路同步塊場景

輕量級鎖 競争的線程不會阻塞,提高了程式的響應速度 如果始終得不到鎖競争的線程,使用自旋會消耗CPU 追求響應時間,同步塊執行速度非常快

重量級鎖 線程競争不使用自旋,不會消耗CPU 線程阻塞,響應時間緩慢 追求吞吐量,同步塊執行速度較長。

優點 缺點 使用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距 如果線程間存在鎖競争,會帶來額外的鎖撤銷的消耗 适用于隻有一個線程通路同步塊場景
輕量級鎖 競争的線程不會阻塞,提高了程式的響應速度 如果始終得不到鎖競争的線程,使用自旋會消耗CPU 追求響應時間,同步塊執行速度非常快
重量級鎖 線程競争不使用自旋,不會消耗CPU

原子操作CAS原理

CAS的原理:CAS操作需要輸入兩個數值,一個舊值(期望操作之前的值)和一個新值,在操作期間先比較舊值有沒有發送變化,如果沒有發生變化,才交換新值,發生了變化則不交換
CAS(Compare And Swap),指令級别保證這是一個原子操作
三個運算符:  一個記憶體位址V,一個期望的值A,一個新值B
基本思路:如果位址V上的值和期望的值A相等,就給位址V賦給新值B,如果不是,不做任何操作。
循環(死循環,自旋)裡不斷的進行CAS操作      

CAS三大問題

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
 
public class testCAS {
/**
* jdk并發包提供一些類支援原子操作 AtomicInteger AtomicBoolean AtomicLong
* 1.AtomicStampedReference增加版本号,可解決ABA問題
* 2.JVM提供的pause指令 可延遲流水線執行指令,避免記憶體順序沖突,可解決循環時間長開銷大問題
* 3.AtomicReference類可保證引用對象的原子性,可以把多個變量放在一個對象進行CAS操作,可解決隻能保證一個共享變量的原子操作問題
*/
  private AtomicInteger atomicI = new AtomicInteger(0);
 
  private int i = 0;
 
  public static void main(String[] args) {
    final testCAS cas = new testCAS();
    List<Thread> ts = new ArrayList<Thread>(600);
    long start = System.currentTimeMillis();
 
    for (int j = 0; j < 100; j++) {
      Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
          for (int i = 0; i < 10000; i++) {
            cas.count();
            cas.safeCount();
          }
        }
      });
      ts.add(t);
    }
    for (Thread t : ts)
      t.start();
    // 等待所有線程執行完成
    for (Thread t : ts) {
      try {
        t.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("cas.i:" + cas.i);
    System.out.println("cas.atomicI.get():" + cas.atomicI.get());
    System.out.println("System.currentTimeMillis() - start:" + (System.currentTimeMillis() - start));
  }
 
  /**
   * 使用CAS實作線程安全的計數器
   */
  protected void safeCount() {
    for (;;) {
      int i = atomicI.get();
      boolean suc = atomicI.compareAndSet(i, ++i);
      if (suc)
        break;
    }
  }
 
  /**
   * 非線程安全計數器
   */
  protected void count() {
    i++;
  }
}      

繼續閱讀