一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 并發編程之Java內存模型鎖的內存語義

并發編程之Java內存模型鎖的內存語義

2022-03-11 00:39李子捌 Java教程

這篇文章主要介紹了并發編程之Java內存模型鎖的內存語義,鎖的作用是讓臨界區互斥執行,本文只要圍繞鎖的內存語義展開全文內容,需要的小伙伴可以參考一下

簡介:

的作用是讓臨界區互斥執行。本文闡述所得另一個重要知識點――鎖的內存語義。

 

1、鎖的釋放-獲取建立的happens-before關系

鎖是Java并發編程中最重要的同步機制。鎖除了讓臨界區互斥執行外,還可以讓釋放鎖的線程向獲取同一個鎖的線程發送消息。

鎖釋放-獲取的示例代碼:

package com.lizba.p1;

/**
* <p>
*      鎖示例代碼
* </p>
*
* @Author: Liziba
* @Date: 2021/6/10 21:43
*/
public class MonitorExample {

  int a = 0;

  public synchronized void writer() {     // 1;
      a++;                                // 2;
  }                                       // 3;

  public synchronized void reader() {     // 4;
      int i = a;                          // 5;
      System.out.println(i);
  }                                       // 6;
  
}

假設線程A執行writer()方法,隨后線程B執行reader()方法。根據happens-before規范,這個過程包含的happens-before關系可以分為3類。

  • 根據程序次序規則:1 happens-before 2,2 happens-before 3, 4 happens-before 5,5 happens-before 6
  • 根據監視器鎖規則:3 happens-before 4
  • 根據happens-before的傳遞性,2 happens-before 5

上述happens-before關系的圖形化表現形式如圖:

并發編程之Java內存模型鎖的內存語義

總結:

線程A在釋放鎖之前所有可見的共享變量,在線程B獲取同一個鎖之后,將立即變得對B線程可見。

 

2、鎖釋放和獲取的內存語義

當線程釋放鎖時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存中。以上述MonitorExample程序為例,A線程釋放鎖后共享數據的狀態

共享數據的狀態示意圖如下所示:

并發編程之Java內存模型鎖的內存語義

當線程獲取鎖時,JMM會把該線程對應的本地內存置為無效。從而使得被監視器鎖保護的臨界區代碼必須從主內存中讀取共享變量。

鎖獲取的狀態示意圖:

并發編程之Java內存模型鎖的內存語義

對比鎖釋放-獲取鎖的內存語義與volatile寫-讀的內存語義可以看出:鎖釋放與volatile寫有相同的內存語義;鎖獲取與volatile讀有相同的內存語義。

總結:

  • 線程A釋放鎖,實質上是線程A向接下來要獲取這個鎖的某個線程發出了(線程A對共享變量所做修改的)消息。
  • 線程B獲取鎖,實質上是線程B接受了之前某個線程發出的(在釋放這個鎖對共享變量鎖做的修改的)消息。
  • 線程A是否鎖,隨后線程B獲取這個鎖,這個過程實質上是線程A通過主內存向線程B發送消息。

 

3、鎖內存的語義實現

分析ReentrantLock的源代碼,來分析鎖內存語義的具體實現機制。

示例代碼:

package com.lizba.p1;

import java.util.concurrent.locks.ReentrantLock;

/**
* <p>
*  ReentrantLock示例代碼
* </p>
*
* @Author: Liziba
* @Date: 2021/6/10 22:17
*/
public class ReentrantLockExample {

  int a = 0;
  ReentrantLock lock = new ReentrantLock();

  public void writer() {
      lock.lock();                 // 獲取鎖
      try {
          a++;
      } finally {
          lock.unlock();          // 釋放鎖
      }
  }

  public void reader() {
      lock.lock();                // 獲取鎖
      try {
          int i = a;
          System.out.println(i);
      } finally {
          lock.unlock();          // 釋放鎖
      }
  }

}

在ReentrantLock中,調用lock()方法獲取鎖;調用unlock()方法釋放鎖。

ReentrantLock的實現依賴于Java同步器框架AbstractQueuedSynchronized(AQS) 。AQS使用一個整型的volatile變量(state)來維護同步狀態,這個volatile變量是ReentrantLock內存語義實現的關鍵。

ReetrantLock的類圖:

并發編程之Java內存模型鎖的內存語義

ReentrantLock分為公平鎖和非公平鎖,首先分析公平鎖。

使用公平鎖時,加鎖方法lock()的調用軌跡如下:

  • ReentrantLock: lock(
  • FairSync: lock()
  • AbstractQueuedSynchronizer: acquire(int arg)
  • ReentrantLock: tryAcquire(int acquires)

第4步開始真的加鎖,下面是該方法的源代碼:

protected final boolean tryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   // 獲取鎖開始,首先讀取volatile變量state
   int c = getState();
   if (c == 0) {
       if (!hasQueuedPredecessors() &&
           compareAndSetState(0, acquires)) {
           setExclusiveOwnerThread(current);
           return true;
       }
   }
   else if (current == getExclusiveOwnerThread()) {
       int nextc = c + acquires;
       if (nextc < 0)
           throw new Error("Maximum lock count exceeded");
       setState(nextc);
       return true;
   }
   return false;
}

從上面的代碼中可以看出,加鎖方法首先讀取volatile變量state。

在使用公平鎖時,解鎖方法unlock()調用軌跡如下:

  • ReentrantLock: unlock()
  • AbstractQueuedSynchronizer: release(int arg)
  • Sync: tryRelease(int release)

第3步開始真的釋放鎖,下面是該方法的源代碼:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 釋放鎖的最后,寫volatile變量state
    setState(c);
    return free;
}

從上面的代碼中可以看出,釋放鎖的最后寫volatile變量state。

總結公平鎖:

根據volatile的happens-before規則,釋放鎖的線程在寫volatile變量之前可見的共享變量,在獲取鎖的線程讀取到同一個volatile變量后將立即變得對獲取鎖的線程可見。

現在分析非公平鎖:

注意:非公平鎖的釋放和公平鎖的釋放完全一致,都是上面的源代碼。所以下面只分析非公平鎖的獲取過程。

使用非公平鎖,加鎖方法lock()的調用軌跡如下:

  • ReentrantLock: lock()
  • NonfairSync: lock()
  • AbstractQueuedSynchronizer: compareAndSetState(int expect, int update)

第3步開始真的加鎖,下面是該方法的源代碼:

// 方法1  
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 此方法中開始加鎖
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

// 方法2
protected final boolean compareAndSetState(int expect, int update) {
   // See below for intrinsics setup to support this
   // 該方法是native方法,在JVM中實現
   return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

該方法以原子操作的方式更新state變量,也就是compareAndSet() (CAS)操作。JDK文檔對該方法說明如下:如果當前狀態值等于預期值,則以原子方式同步狀態設置為給定更新的值。此操作具有volatile讀和寫的內存語義。

接下來分別從編譯器和處理器的角度來分析,CAS如何同時具有volatile讀和volatile寫的內存語義。

編譯器的角度:

前文已經講過,編譯器不會對volatile讀與volatile讀后面的任意內存操作重排序;編譯器不會對volatile寫和volatile寫后前面的任意內存操作重排序。組合這兩個條件,意味著同時實現volatile讀和volatile寫的內存語義,編譯器不能對CAS與CAS前面和后面任意內存操作重排序。

處理器的角度:

(本人不太懂C++)這一塊總結需要看JVM源碼,可能會總結錯誤,如需要深入理解這一塊請查看《Java并發編程藝術》53頁。

sun.misc.Unsafe中的compareAndSwapInt源碼如下:(不懂Unsafe請看往期文章)

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

這是一個本地方法。這個本地方法會在openJDK中調用C++代碼,假設當前是X86處理器,程序會根據當前處理器的類型來決定是非cmpxchg指令添加lock前綴。

  • 程序運行在多處理器上,就為cmpxchg指令加上lock前綴(Lock Cmpxchg)
  • 程序運行在單處理器上,就省略lock前綴(單處理器自身會維護單處理器內的順序一致性,不需要lock前綴提供的內存屏障效果)

intel手冊對lock前綴的說明:

  • 對內存的讀-改-寫操作原子執行。(總線鎖定/緩存鎖定)
  • 禁止該指令,與之前的讀和寫指令重排序
  • 把寫緩沖區的所有數據刷新到內存中

上面的2、3兩點所具有的內存屏障的效果,足以同時實現volatile讀和volatile寫的內存語義。所以JDK文檔說CAS 具有volatile讀和volatile寫的內存語義對于處理器也是符合的。

公平鎖和非公平鎖的總結:

  • 公平鎖和非公平鎖的釋放,最后都需要寫一個volatile變量state
  • 公平鎖獲取時,首先會去讀volatile變量
  • 非公平鎖獲取鎖時,首先會用CAS更新volatile變量,這個操作同時具有volatile讀和volatile寫的內存語義

釋放鎖-獲取鎖的內存語義的實現方式總結 :

  • 利用volatile變量的寫-讀所具有的內存語義
  • 利用CAS所附帶的volatile讀和volatile寫的內存語義

 

4、concurrent包的實現

由于Java的CAS同時具有volatile讀和volatile寫的內存語義,因此Java線程之間的通信方式有以下4種方式

  • A線程寫volatile變量,隨后B線程讀這個volatile變量
  • A線程寫volatile變量,隨后B線程用CAS更新這個volatile變量
  • A線程利用CAS更新一個volatile變量,隨后B線程用CAS更新這個volatile變量
  • A線程利用CAS更新一個volatile變量,隨后B線程讀這個volatile變量

Java的CAS會使用現代處理器上提供的高效機器級別的原子指令,這些原子指令以原子方式對內存執行讀-改-寫操作,這是在多處理器實現同步的關鍵。同時volatile變量的讀/寫和CAS可以實現線程之間的通信。這些特性就是Java整個concurrent包的基石。

concurrent包的通用化實現模式:

  • 聲明共享變量volatile
  • 使用CAS的原子條件更新來實現線程之間的同步
  • 配合volatile的讀/寫和CAS具有的volatile讀和寫的內存語義來實現線程之間的通信。

AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)、非阻塞數據結構和原子變量類(java.util.concurrent.atomic包中的類),這些concurrent包中基礎類都是使用這個模式來實現的,而concurrent包中的高層類又是依賴于這些基礎類。

圖示concurrent包的實現示意圖:

并發編程之Java內存模型鎖的內存語義

到此這篇關于并發編程之Java內存模型鎖的內存語義的文章就介紹到這了,更多相關Java內存模型鎖的內存語義內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://juejin.cn/post/7017979350410592292

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产午夜精品不卡视频 | 色四虎 | 亚洲国产在线播放在线 | 99热这里只有精品国产免费 | 亚洲精品卡1卡二卡3卡四卡 | 性奶乳妇 | 亚洲精品成人456在线播放 | 美女黑人做受xxxxxⅹ | 免费观看无人区完整版 | 97超pen个人视频公开视频视 | 欧亚精品一区二区三区 | sedog在线长片 | 任我鲁精品视频精品 | a v在线男人的天堂观看免费 | 男插女的下面免费视频夜色 | yellow最新视频2019 | 免费一区二区 | 波多野给衣一区二区三区 | 免费大秀视频在线播放 | 91精品啪在线观看国产老湿机 | 国产精品视频一区二区三区不卡 | 亚洲AV无码A片在线观看蜜桃 | 亚洲AV永久无码精品老司机蜜桃 | 四虎成人4hutv影院 | 国产精品在线 | 韩国帅男同gay网站 韩国三级在线播放 | 欧美精品成人a多人在线观看 | 国产成人精品一区二三区2022 | 给我一个黄色网址 | 精品久久成人免费第三区 | 色在线看 | 521色香蕉网站在线观看 | 114级毛片免费观看 1024亚洲天堂 | 新版孕妇bbwbbwbbw | 动漫美女人物被黄漫小说 | 亚洲男人天堂 | 日本高清无吗 | girlfriend动漫在线播放 | jizz中国jizz老师水多 | 成人在线一区二区 | 毛片a级放荡的护士hd |