溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

深入淺出了解happens-before原則

發布時間:2020-10-18 20:58:55 來源:腳本之家 閱讀:236 作者:zdxiq000 欄目:編程語言

看Java內存模型(JMM, Java Memory Model)時,總有一個困惑。關于線程、主存(main memory)、工作內存(working memory),我都能找到實際映射的硬件:線程可能對應著一個內核線程,主存對應著內存,而工作內存則涵蓋了寫緩沖區、緩存(cache)、寄存器等一系列為了提高數據存取效率的暫存區域。但是,一提到happens-before原則,就讓人有點“丈二和尚摸不著頭腦”。這個涵蓋了整個JMM中可見性原則的規則,究竟如何理解,把我個人一些理解記錄下來。

兩個操作間具有happens-before關系,并不意味著前一個操作必須要在后一個操作之前執行。happens-before僅僅要求前一個操作對后一個操作可見。

這個說法我先后在好幾本書中都看到過。也就是說,happens-before原則和一般意義上的時間先后是不同的。那究竟是什么呢?一步步來看。

順序一致性內存模型

我們先來看一個理想化的模型:順序一致性(Sequentially Consistent)內存模型。在這個模型里,所有操作按程序的順序來執行,并且每一個操作都是原子的,且立即對所有線程可見。 
 深入淺出了解happens-before原則

這個系統中同一時間只有一個線程能讀或寫內存。也就是說,這個系統里的每兩個指令之間,都嚴格按執行的先后,具有著happens-before關系。所有的線程,都能夠看到一致的全局指令執行視圖。如果將總線1看做是線程和內存之間的通道,那么順序一致性模型就相當于在所有讀/寫內存的操作時,鎖住總線。

特別注意一點,順序一致性模型,不代表多線程沒有同步問題,只是每個操作之間不存在同步問題,如果你的操作是多個操作的集合體,照樣不能安全工作。圖中所示的是常見的自增操作,兩個線程都有同樣的執行視圖:1->2->3->4->5->6。然而,線程A的寫結果,依然被線程B所覆蓋了。A線程讀寫固然對B線程立即可見,但是由于5/6的寫操作對于內存的影響依賴于1/2的讀操作,所以對于多線程仍然存在問題。

深入淺出了解happens-before原則

顯然,順序一致性模型是一種犧牲并行度、換取多線程對共享內存的可見性的一種理想模型。從JMM實現volatile以及synchronized的內存語義的方式,正是鎖住總線或者說鎖住線程自身存儲(指working memory)。

Java內存模型

關于Java內存模型的書籍文章,汗牛充棟,想必大家也都有自己的理解。那就僅僅由上面的順序一致性模型來引出JMM,看看具體區別在哪。

深入淺出了解happens-before原則

可以看出,工作內存是一個明顯區別于順序一致性內存模型的地方。事實上,造成可見性問題的根源之一,就在于這個工作內存(強調一下,包括緩存、寫緩沖和寄存器等等)。工作內存使得每個線程都有了自己的私有存儲,大部分時間對數據的存取工作都在這個區域完成。但是我們寫一個數據,是直到數據寫到主存中才算真正完成。實際上每個線程維護了一個副本,所有線程都在自己的工作內存中不斷地讀/寫一個共享內存中的數據的副本。單線程情況下,這個副本不會造成任何問題;但一旦到多線程,有一個線程將變量寫到主存,其他線程卻不知道,其他線程的副本就都過期。比如,由于工作內存的存在,程序員寫的一段代碼,寫一個普通的共享變量,其可能先被寫到緩沖區,那指令完成的時間就被推遲了,實際表現也就是我們常說的“指令重排序”(這實際上是內存模型層面的重排序,重排序還可能是編譯器、機器指令層級上的亂序)。

因此,在Java內存模型中,每個線程不再像順序一致性模型中那樣有確定的指令執行視圖,一個指令可能被重排了。從一個線程的角度看,其他線程(甚至是這個線程本身)執行的指令順序有多種可能性,也就是說,一個線程的執行結果對其他線程的可見性無法保證。

總結一下導致可見性問題的原因:

1.數據的寫無法及時通知到別的線程,如寫緩沖區的引入
2.線程不能及時讀到其他線程對共享變量的修改,如緩存的使用
3.各種層級上對指令的重排序,導致指令執行的順序無法確定

所以要解決可見性問題,本質是要讓線程對共享變量的修改,及時同步到其他線程。我們所使用的硬件架構下,不具備順序一致性內存模型的全局一致的指令執行順序,討論指令執行的時間先后并不存在意義或者說根本沒辦法確定時間上的先后??梢钥纯聪旅娉绦?,每個線程中的flag副本會在多久后被更新呢?答案是:無法確定,看線程何時刷新自己的工作內存。

public class testVisibility {
 public static boolean flag = false;

 public static void main(String[] args) {
  List<Thread> thdList = new ArrayList<Thread>();
  for(int i = 0; i < 10; i++) {
   Thread t = new Thread(new Runnable(){
    public void run() {
     while (true) {
      if (flag) {
       // 多運行幾次,可能并不會打印出來也可能會打印出來
       // 如果不打印,則表示Thread看到的仍然是工作內存中的flag
       // 可以嘗試將flag變成volatile再運行幾次看看
         System.out.println(Thread.currentThread().getId() + " is true now"); 
      }
     }
    }
   });
   t.start();
   thdList.add(t);
  }

  flag = true;
  System.out.println("set flag true");

  // 等待線程執行完畢
  try {
   for (Thread t : thdList) {
    t.join();
   }
  } catch (Exception e) {

  }
 }
}

那么既然我們無法討論指令執行的先后,也不需要討論,我們實際只想知道某線程的操作對另一個線程是否可見,于是就規定了happens-before這個可見性原則,程序員可以基于這個原則進行可見性的判斷。

volatile變量

volatile就是一個踐行happens-before的關鍵字??匆韵聦olatile的描述,就不難知道,happens-before指的是線程接收其他線程修改共享變量的消息與該線程讀取共享變量的先后關系。大家可以再細想一下,如果沒有happens-before原則,豈不是相當于一個線程讀取自己的共享變量副本時,其他線程修改這個變量的消息還沒有同步過來?這就是可見性問題。

volatile變量規則:對一個volatile的寫,happens-before于任意后續對這個volatile變量的讀。
線程A寫一個volatile變量,實質上是線程A向接下來要獲取這個鎖的某個線程發出了(線程A對共享變量修改的)消息。
線程B讀一個volatile變量,實質上是線程B接收了之前某個線程發出的(對共享變量所做修改的)消息。
線程A寫一個volatile變量,隨后線程B讀這個變量,這個過程實質上是線程A通過主內存向線程B發送消息。

其實仔細看看volatile的實現方式,實際上就是限制了重排序的范圍——加入內存屏障(Memory Barrier or Memory Fence)。也即是說,允許指令執行的時間先后順序在一定范圍內發生變化,而這個范圍就是根據happens-before原則來規定。內存屏障概括起來有兩個功能:

1.使寫緩沖區的內容刷新到內存,保證對其他線程/CPU可見
2.禁止讀寫操作的越過內存屏障進行重排序

而這上述功能組合起來,就完成上面所說的happens-before所表達的線程通信過程。

每個volatile寫操作的前面插入一個StoreStore屏障
每個volatile寫操作的后面插入一個StoreLoad屏障
每個volatile讀操作的后面插入一個LoadLoad屏障
每個volatile讀操作的后面插入一個LoadStore屏障

關于內存屏障的種類,這里不是研究的重點。一直困擾我的是,在多處理器系統下,這個屏障如何能跨越處理器來阻止操作執行的順序呢?比如下面的讀寫操作:

public static volatile int race = 0;
// Thread A
public static void save(int src) {
 race = src;
}
// Thread B
public static int load() {
 return race;
}

這就要提到從操作系統到硬件層面的觀念轉換,可以參看總線事務(Bus transaction)的概念。當CPU要與內存進行數據交換的時候,實際上總線會同步數據交換操作,同一時刻只能有一個CPU進行讀/寫內存,所以我們所看到的多處理器并行,并行的是CPU的計算資源。在總線看來,對于存儲的讀寫操作就是串行的,是按照一定順序的。這也就是為什么一個內存屏障能夠跨越處理器去限制讀寫、去完成通信。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女