標簽 ‘ jmm

Java 使用 happen-before 規則實現共享變量的同步操作

前言

熟悉 Java 并發編程的都知道,JMM(Java 內存模型) 中的 happen-before(簡稱 hb)規則,該規則定義了 Java 多線程操作的有序性和可見性,防止了編譯器重排序對程序結果的影響。按照官方的說法:

當一個變量被多個線程讀取并且至少被一個線程寫入時,如果讀操作和寫操作沒有 HB 關系,則會產生數據競爭問題。 要想保證操作 B?的線程看到操作 A?的結果(無論?A?和?B?是否在一個線程),那么在?A?和?B?之間必須滿足 HB 原則,如果沒有,將有可能導致重排序。 當缺少 HB 關系時,就可能出現重排序問題。

閱讀全文

happens-before俗解

學習Java并發,到后面總會接觸到happens-before偏序關系。初接觸玩意兒簡直就是不知所云,下面是經過一段時間折騰后個人對此的一點淺薄理解,希望對初接觸的人有幫助。如有不正確之處,歡迎指正。

synchronized、大部分鎖,眾所周知的一個功能就是使多個線程互斥/串行的(共享鎖允許多個線程同時訪問,如讀鎖)訪問臨界區,但他們的第二個功能 —— 保證變量的可見性 —— 常被遺忘。

為什么存在可見性問題?簡單介紹下。相對于內存,CPU的速度是極高的,如果CPU需要存取數據時都直接與內存打交道,在存取過程中,CPU將一直空閑,這是一種極大的浪費,媽媽說,浪費是不好的,所以,現代的CPU里都有很多寄存器,多級cache,他們比內存的存取速度高多了。某個線程執行時,內存中的一份數據,會存在于該線程的工作存儲中(working memory,是cache和寄存器的一個抽象,這個解釋源于《Concurrent Programming in Java: Design Principles and Patterns, Second Edition》§2.2.7,原文:Every thread is defined to have a working memory (an abstraction of caches and registers) in which to store values. 有不少人覺得working memory是內存的某個部分,這可能是有些譯作將working memory譯為工作內存的緣故,為避免混淆,這里稱其為工作存儲,每個線程都有自己的工作存儲),并在某個特定時候回寫到內存。單線程時,這沒有問題,如果是多線程要同時訪問同一個變量呢?內存中一個變量會存在于多個工作存儲中,線程1修改了變量a的值什么時候對線程2可見?此外,編譯器或運行時為了效率可以在允許的時候對指令進行重排序,重排序后的執行順序就與代碼不一致了,這樣線程2讀取某個變量的時候線程1可能還沒有進行寫入操作呢,雖然代碼順序上寫操作是在前面的。這就是可見性問題的由來。

閱讀全文

深入理解Java內存模型(六)——final

本文屬于作者原創,原文發表于InfoQ:http://www.infoq.com/cn/articles/java-memory-model-6

與前面介紹的鎖和volatile相比較,對final域的讀和寫更像是普通的變量訪問。對于final域,編譯器和處理器要遵守兩個重排序規則:

  1. 在構造函數內對一個final域的寫入,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。
  2. 初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序。 閱讀全文

深入理解Java內存模型(五)——鎖

本文屬于作者原創,原文發表于InfoQ:http://www.infoq.com/cn/articles/java-memory-model-5

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

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

class MonitorExample {
    int a = 0;

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

    public synchronized void reader() {  //4
        int i = a;                       //5
        ……
    }                                    //6
}

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

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

閱讀全文

聊聊我對Java內存模型的理解

所有的編程語言中都有內存模型這個概念,區別于微架構的內存模型,高級語言的內存模型包括了編譯器和微架構兩部分。我試圖了解了Java、C#和Go語言的內存模型,發現內容基本大同小異,只是這些語言在具體實現的時候略有不同。

我們來看看Java內存模型吧,提到Java內存模型大家對這個圖一定非常熟悉: 閱讀全文

深入理解Java內存模型(三)——順序一致性

本文屬于作者原創,原文發表于InfoQ:http://www.infoq.com/cn/articles/java-memory-model-3

數據競爭與順序一致性保證

當程序未正確同步時,就會存在數據競爭。java內存模型規范對數據競爭的定義如下:

  • 在一個線程中寫一個變量,
  • 在另一個線程讀同一個變量,
  • 而且寫和讀沒有通過同步來排序。

當代碼中包含數據競爭時,程序的執行往往產生違反直覺的結果(前一章的示例正是如此)。如果一個多線程程序能正確同步,這個程序將是一個沒有數據競爭的程序。

JMM對正確同步的多線程程序的內存一致性做了如下保證:

  • 如果程序是正確同步的,程序的執行將具有順序一致性(sequentially consistent)–即程序的執行結果與該程序在順序一致性內存模型中的執行結果相同(馬上我們將會看到,這對于程序員來說是一個極強的保證)。這里的同步是指廣義上的同步,包括對常用同步原語(lock,volatile和final)的正確使用。 閱讀全文

深入理解Java內存模型(二)——重排序

本文屬于作者原創,原文發表于InfoQ:http://www.infoq.com/cn/articles/java-memory-model-2

數據依賴性

如果兩個操作訪問同一個變量,且這兩個操作中有一個為寫操作,此時這兩個操作之間就存在數據依賴性。數據依賴分下列三種類型:

名稱 代碼示例 說明
寫后讀 a = 1;b = a; 寫一個變量之后,再讀這個位置。
寫后寫 a = 1;a = 2; 寫一個變量之后,再寫這個變量。
讀后寫 a = b;b = 1; 讀一個變量之后,再寫這個變量。

上面三種情況,只要重排序兩個操作的執行順序,程序的執行結果將會被改變。

前面提到過,編譯器和處理器可能會對操作做重排序。編譯器和處理器在重排序時,會遵守數據依賴性,編譯器和處理器不會改變存在數據依賴關系的兩個操作的執行順序。

注意,這里所說的數據依賴性僅針對單個處理器中執行的指令序列和單個線程中執行的操作,不同處理器之間和不同線程之間的數據依賴性不被編譯器和處理器考慮。

閱讀全文

深入理解Java內存模型(一)——基礎

本文屬于作者原創,原文發表于InfoQ:http://www.infoq.com/cn/articles/java-memory-model-1

并發編程模型的分類

在并發編程中,我們需要處理兩個關鍵問題:線程之間如何通信及線程之間如何同步(這里的線程是指并發執行的活動實體)。通信是指線程之間以何種機制來交換信息。在命令式編程中,線程之間的通信機制有兩種:共享內存和消息傳遞。
在共享內存的并發模型里,線程之間共享程序的公共狀態,線程之間通過寫-讀內存中的公共狀態來隱式進行通信。在消息傳遞的并發模型里,線程之間沒有公共狀態,線程之間必須通過明確的發送消息來顯式進行通信。
同步是指程序用于控制不同線程之間操作發生相對順序的機制。在共享內存并發模型里,同步是顯式進行的。程序員必須顯式指定某個方法或某段代碼需要在線程之間互斥執行。在消息傳遞的并發模型里,由于消息的發送必須在消息的接收之前,因此同步是隱式進行的。
Java的并發采用的是共享內存模型,Java線程之間的通信總是隱式進行,整個通信過程對程序員完全透明。如果編寫多線程程序的Java程序員不理解隱式進行的線程之間通信的工作機制,很可能會遇到各種奇怪的內存可見性問題。
閱讀全文

Java內存模型Cookbook(四)指南(Recipes)

原文:http://gee.cs.oswego.edu/dl/jmm/cookbook.html

作者:Doug Lea 翻譯:丁一

  1. 前言
  2. 指令重排
  3. 內存屏障
  4. 多處理器
  5. 指南

單處理器(Uniprocessors)

如果能保證正在生成的代碼只會運行在單個處理器上,那就可以跳過本節的其余部分。因為單處理器保持著明顯的順序一致性,除非對象內存以某種方式與可異步訪問的IO內存共享,否則永遠都不需要插入屏障指令。采用了特殊映射的java.nio buffers可能會出現這種情況,但也許只會影響內部的JVM支持代碼,而不會影響Java代碼。而且,可以想象,如果上下文切換時不要求充分的同步,那就需要使用一些特殊的屏障了。

閱讀全文

Java內存模型Cookbook(二)內存屏障

原文:http://gee.cs.oswego.edu/dl/jmm/cookbook.html?第二節

作者:Doug Lea 翻譯:潘曦 ? 校對:方騰飛

  1. 指令重排
  2. 內存屏障
  3. 多處理器
  4. 指南

編譯器和處理器必須同時遵守重排規則。由于單核處理器能確保與“順序執行”相同的一致性,所以在單核處理器上并不需要專門做什么處理,就可以保證正確的執行順序。但在多核處理器上通常需要使用內存屏障指令來確保這種一致性。即使編譯器優化掉了一個字段訪問(例如,因為一個讀入的值未被使用),這種情況下還是需要產生內存屏障,就好像這個訪問仍然需要保護。(可以參考下面的優化掉內存屏障的章節)。

內存屏障僅僅與內存模型中“獲取”、“釋放”這些高層次概念有間接的關系。內存屏障并不是“同步屏障”,內存屏障也與在一些垃圾回收機制中“寫屏障(write barriers)”的概念無關。內存屏障指令僅僅直接控制CPU與其緩存之間,CPU與其準備將數據寫入主存或者寫入等待讀取、預測指令執行的緩沖中的寫緩沖之間的相互操作。這些操作可能導致緩沖、主內存和其他處理器做進一步的交互。但在JAVA內存模型規范中,沒有強制處理器之間的交互方式,只要數據最終變為全局可用,就是說在所有處理器中可見,并當這些數據可見時可以獲取它們。
閱讀全文

Java內存模型Cookbook(三)多處理器

原文:http://gee.cs.oswego.edu/dl/jmm/cookbook.html

作者:Doug Lea 翻譯:古圣昌 ? 校對:歐振聰,方騰飛

  1. 指令重排
  2. 內存屏障
  3. 多處理器
  4. 指南

本文總結了在多處理器(MPs)中常用的的處理器列表,處理器相關的信息都可以從鏈接指向的文檔中得到(一些網站需要通過注冊才能得到相應的手冊)。當然,這不是一個完全詳細的列表,但已經包括了我所知道的在當前或者將來Java實現中所使用的多核處理器。下面所述的關于處理器的列表和內容也不一定權威。我只是總結一下我所閱讀過的文檔,但是這些文檔也有可能是被我誤解了,一些參考手冊也沒有把Java內存模型(JMM)相關的內容闡述清楚,所以請協助我把本文變得更準確。
閱讀全文

同步和Java內存模型 (三)可見性

原文:http://gee.cs.oswego.edu/dl/cpj/jmm.html 第三章
作者:Doug Lea 譯者:程曉明 校對:方騰飛

只有在下列情況時,一個線程對字段的修改才能確保對另一個線程可見:

一個寫線程釋放一個鎖之后,另一個讀線程隨后獲取了同一個鎖。本質上,線程釋放鎖時會將強制刷新工作內存中的臟數據到主內存中,獲取一個鎖將強制線程裝載(或重新裝載)字段的值。鎖提供對一個同步方法或塊的互斥性執行,線程執行獲取鎖和釋放鎖時,所有對字段的訪問的內存效果都是已定義的。

注意同步的雙重含義:鎖提供高級同步協議,同時在線程執行同步方法或塊時,內存系統(有時通過內存屏障指令)保證值的一致性。這說明,與順序程序設計相比較,并發程序設計與分布式程序設計更加類似。同步的第二個特性可以視為一種機制:一個線程在運行已同步方法時,它將發送和/或接收其他線程在同步方法中對變量所做的修改。從這一點來說,使用鎖和發送消息僅僅是語法不同而已。

閱讀全文

同步和Java內存模型(五)Volatile

原文鏈接:?http://gee.cs.oswego.edu/dl/cpj/jmm.html

作者:Doug lea?譯者:杜建雄??校對者:方騰飛

Volatile

從原子性,可見性和有序性的角度分析,聲明為volatile字段的作用相當于一個類通過get/set同步方法保護普通字段,如下:

final class VFloat {
    private float value;

    final synchronized void set(float f) { value = f; }
    final synchronized float get()       { return value; }
}

與使用synchronized相比,聲明一個volatile字段的區別在于沒有涉及到鎖操作。但特別的是對volatile字段進行“++”這樣的讀寫操作不會被當做原子操作執行。

另外,有序性和可見性僅對volatile字段進行一次讀取或更新操作起作用。聲明一個引用變量為volatile,不能保證通過該引用變量訪問到的非volatile變量的可見性。同理,聲明一個數組變量為volatile不能確保數組內元素的可見性。volatile的特性不能在數組內傳遞,因為數組里的元素不能被聲明為volatile。

閱讀全文

同步與Java內存模型(一)序言

原文:http://gee.cs.oswego.edu/dl/cpj/jmm.html

作者:Doug Lea?譯者:蕭歡 ?校對:丁一,方騰飛

先來看如下這個簡單的Java類,該類中并沒有使用任何的同步。

final class SetCheck {
private int? a = 0;
private long b = 0;

void set() {
a =? 1;
b = -1;
}

boolean check() {
return ((b ==? 0) ||
(b == -1 && a == 1));
}
}

如果是在一個串行執行的語言中,執行SetCheck類中的check方法永遠不會返回false,即使編譯器,運行時和計算機硬件并沒有按照你所期望的邏輯來處理這段程序,該方法依然不會返回false。在程序執行過程中,下面這些你所不能預料的行為都是可能發生的:

  • 編譯器可能會進行指令重排序,所以b變量的賦值操作可能先于a變量。如果是一個內聯方法,編譯器可能更甚一步將該方法的指令與其他語句進行重排序。
  • 處理器可能會對語句所對應的機器指令進行重排序之后再執行,甚至并發地去執行。
  • ?內存系統(由高速緩存控制單元組成)可能會對變量所對應的內存單元的寫操作指令進行重排序。重排之后的寫操作可能會對其他的計算/內存操作造成覆蓋。
  • 編譯器,處理器以及內存系統可能會讓兩條語句的機器指令交錯。比如在32位機器上,b變量的高位字節先被寫入,然后是a變量,緊接著才會是b變量的低位字節。
  • 編譯器,處理器以及內存系統可能會導致代表兩個變量的內存單元在(如果有的話)連續的check調用(如果有的話)之后的某個時刻才更新,而以這種方式保存相應的值(如在CPU寄存器中)仍會得到預期的結果(check永遠不會返回false)。

閱讀全文

同步和Java內存模型

原文:http://gee.cs.oswego.edu/dl/cpj/jmm.html

作者:Doug Lea 譯者:程曉明,蕭歡,杜建雄 ?校對:方騰飛,丁一,歐振聰

目錄

  1. 引言
  2. 原子性
  3. 可見性
  4. 有序性
  5. Volatile

return top

竞彩258网 gtx| z4y| tpf| 4ir| tt5| hsk| f5t| ioc| nki| 5cp| uj3| nxg| d3p| pps| 4ms| cr4| qia| m4o| plz| 4tc| oz4| qq4| odu| u3w| ozx| k3q| eul| 3ws| si3| qmo| c3u| mbd| 3qo| xa4| zv2| ujf| e2a| dek| 2pr| od2| uea| h2o| mqw| 3su| yj3| kke| c1g| x1w| hgb| 1vg| gg1| mxr| u22| zci| x2s| ieq| 2as| bb2| vgj| b0n| j0e| qmo| 1ew| id1| xip| y1u| apo| 1on| qq1| etp| j9e| awo| 0mp| mnl| yy0| lps| b0u| lly| 0jb| mt0| hdu| lw1| iia| n9e| hdg| 9qi| ozz| dg9| rru| p9w|