成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

[Java并發(fā)-2]Java如何解決可見性問題的

lk20150415 / 1453人閱讀

摘要:誕生之處就支持多線程,所以自然有解決這些問題的辦法,而且在編程語言領(lǐng)域處于領(lǐng)先地位。,線程規(guī)則這條是關(guān)于線程啟動的。在語言里面,的語義本質(zhì)上是一種可見性,意味著事件對事件來說是可見的,無論事件和事件是否發(fā)生在同一個線程里。

之前我們說了:
1,可見性
2,原子性
3,有序性
3個并發(fā)BUG的之源,這三個也是編程領(lǐng)域的共性問題。Java誕生之處就支持多線程,所以自然有解決這些問題的辦法,而且在編程語言領(lǐng)域處于領(lǐng)先地位。理解Java解決并發(fā)問題的方案,對于其他語言的解決方案也有觸類旁通的效果。

什么是Java內(nèi)存模型

我們已經(jīng)知道了,導(dǎo)致可見性的原因是緩存,導(dǎo)致有序性的問題是編譯優(yōu)化。那解決問題的辦法就是直接禁用 緩存和編譯優(yōu)化。但是直接不去使用這些是不行了,性能無法提升。
所以合理的方案是 按需禁用緩存和編譯優(yōu)化。如何做到“按需禁用”,只有編寫代碼的程序員自己知道,所以程序需要給程序員按需禁用和編譯優(yōu)化的方法才行。

Java的內(nèi)存模型如果站在程序員的角度,可以理解為,Java內(nèi)存模型規(guī)范了JVM如何提供按需禁用緩存和編譯優(yōu)化的方法。具體來說,這些方法包括volatile,synchronizedfinal三個關(guān)鍵字段。
以及六項 Happens-Before 規(guī)則。

使用volatile的困惑

volatile 關(guān)鍵字并不是 Java 語言特有的,C語言也有,它的原始意義就是禁用CPU緩存。

例如,我們聲明一個volatile變量 ,volatile int x = 0,它表達的是:告訴編譯器,對這個變量的讀寫,不能使用 CPU 緩存,必須從內(nèi)存中讀取或者寫入??雌饋碚Z義很明確,實際情況比較困惑。

看下以下代碼:

class VolatileExample {

  int x = 0;

  volatile boolean v = false;

  public void writer() {

    x = 42;

    v = true;

  }

  public void reader() {

    if (v == true) {

      // 這里 x 會是多少呢?

    }

  }

}

直覺上看,這里的X應(yīng)該是42,那實際應(yīng)該是多少呢?這個要看Java的版本,如果在低于 1.5 版本上運行,x 可能是42,也有可能是 0;如果在 1.5 以上的版本上運行,x 就是等于 42。
分析一下,為什么 1.5 以前的版本會出現(xiàn) x = 0 的情況呢?因為變量 x 可能被 CPU 緩存而導(dǎo)致可見性問題。這個問題在 1.5 版本已經(jīng)被圓滿解決了。Java 內(nèi)存模型在 1.5 版本對 volatile 語義進行了增強。怎么增強的呢?答案是一項 Happens-Before 規(guī)則。

Happens-Before 規(guī)則

這里直接給出定義:

Happens-Before :前面一個操作的結(jié)果對后續(xù)操作是可見的。

再進一步的講:Happens-Before 約束了編譯器的優(yōu)化行為,雖允許編譯器優(yōu)化,但是要求編譯器優(yōu)化后一定遵守 Happens-Before 規(guī)則。

看一看Java內(nèi)存模型定義了哪些重要的Happens-Before規(guī)則

1,程序的順序性規(guī)則
這條規(guī)則是指在一個線程中,按照程序順序,前面的操作 Happens-Before 于后續(xù)的任意操作。比如剛才那段示例代碼,按照程序的順序,第 6 行代碼 x = 42; Happens-Before 于第 7 行代碼 v = true;,這就是規(guī)則 1 的內(nèi)容,也比較符合單線程里面的思維:程序前面對某個變量的修改一定是對后續(xù)操作可見的。

2,volatile 變量規(guī)則

這條規(guī)則是指對一個 volatile 變量的寫操作,Happens-Before 于后續(xù)對這個 volatile 變量的讀操作。

這個就有點費解了,對一個 volatile 變量的寫操作相對于后續(xù)對這個 volatile 變量的讀操作可見。

3,傳遞性

這條規(guī)則是指如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。

我們將規(guī)則 3 的傳遞性應(yīng)用到我們的例子中,可以看下面這幅圖:

class VolatileExample {

  int x = 0;

  volatile boolean v = false;

  public void writer() {

    x = 42;

    v = true;

  }

  public void reader() {

    if (v == true) {

      // 這里 x 會是多少呢?

    }

  }

}

從圖中可以看到
1,x=42 Happens-Before 寫 v=true,這是規(guī)則1
2,讀v=true Happens-Before 讀變量X,這是規(guī)則2

結(jié)合傳遞性讀定義,即:

線程A的 x=42 Happens-Before 線程B的 讀變量X

java 1.5對 volatile 的增強就是這個,根據(jù)這個定義就保證了之前的 x=42的成立

4,管程中鎖的規(guī)則

這條規(guī)則是指對一個鎖的解鎖 Happens-Before 于后續(xù)對這個鎖的加鎖。

管程 (英語:Moniters,也稱為監(jiān)視器) 是一種程序結(jié)構(gòu),結(jié)構(gòu)內(nèi)的多個子程序(對象或模塊)形成的多個工作線程互斥訪問共享資源。

管程 在 Java 中指的就是 synchronized,synchronized 是 Java 里對管程的實現(xiàn)。
管程中的鎖在 Java 里是隱式實現(xiàn)的,例如下面的代碼,在進入同步塊之前,會自動加鎖,而在代碼塊執(zhí)行完會自動釋放鎖,加鎖以及釋放鎖都是編譯器幫我們實現(xiàn)的。

synchronized (this) { // 此處自動加鎖

  // x 是共享變量, 初始值 =10

  if (this.x < 12) {

    this.x = 12; 

  }  

} // 此處自動解鎖

所以結(jié)合規(guī)則定義,可以這樣理解:假設(shè) x 的初始值是 10,線程 A 執(zhí)行完代碼塊后 x 的值會變成 12(執(zhí)行完自動釋放鎖),線程 B 進入代碼塊時,能夠看到線程 A 對 x 的寫操作,也就是線程 B 能夠看到 x==12。這個也是符合我們直覺的,應(yīng)該不難理解。

5,線程 start() 規(guī)則

這條是關(guān)于線程啟動的。它是指主線程 A 啟動子線程 B 后,子線程 B 能夠看到主線程在啟動子線程 B 前的操作。

換句話說就是,如果線程 A 調(diào)用線程 B 的 start() 方法(即在線程 A 中啟動線程 B),那么該 start() 操作 Happens-Before 于線程 B 中的任意操作。具體可參考下面示例代碼。

Thread B = new Thread(()->{

  // 主線程調(diào)用 B.start() 之前

  // 所有對共享變量的修改,此處皆可見

  // 此例中,var==77

});

// 此處對共享變量 var 修改

var = 77;

// 主線程啟動子線程

B.start();

6,線程 join() 規(guī)則

這條是關(guān)于線程等待的。它是指主線程 A 等待子線程 B 完成(主線程 A 通過調(diào)用子線程 B 的 join() 方法實現(xiàn)),當(dāng)子線程 B 完成后(主線程 A 中 join() 方法返回),主線程能夠“看到”子線程的操作。這里的“看到”,指的是子線程對共享變量的操作。

換句話說就是,如果在線程 A 中,調(diào)用線程 B 的 join() 并成功返回,那么線程 B 中的任意操作 Happens-Before 于該 join() 操作的返回。具體可參考下面示例代碼。

Thread B = new Thread(()->{

  // 此處對共享變量 var 修改

  var = 66;

});

// 例如此處對共享變量修改,

// 則這個修改結(jié)果對線程 B 可見

// 主線程啟動子線程

B.start();

B.join()

// 子線程所有對共享變量的修改

// 在主線程調(diào)用 B.join() 之后皆可見

// 此例中,var==66
過度優(yōu)化的 final

前面我們講 volatile 為的是禁用緩存以及編譯優(yōu)化,那 final關(guān)鍵字 就是告訴編譯器優(yōu)化得更好一點。

final 修飾變量時,初衷是告訴編譯器:這個變量生而不變,可以盡量優(yōu)化。但是Java編譯器在 1.5 以前的版本導(dǎo)致優(yōu)化錯誤了。

構(gòu)造函數(shù)的錯誤重排導(dǎo)致線程可能看到 final 變量的值會變化。詳細(xì)的案例可以參考:http://www.cs.umd.edu/~pugh/j...

當(dāng)然了,在 1.5 以后 Java 內(nèi)存模型對 final 類型變量的重排進行了約束。現(xiàn)在只要我們提供正確構(gòu)造函數(shù)沒有“逸出”,就不會出問題了。

在下面例子中,在構(gòu)造函數(shù)里面將 this 賦值給了全局變量 global.obj,這就是“逸出”,線程通過 global.obj 讀取 x 是有可能讀到 0 的。因此我們一定要避免“逸出”。

final int x;

// 錯誤的構(gòu)造函數(shù)

public FinalFieldExample() { 

  x = 3;

  y = 4;

  // 此處就是講 this 逸出,

  global.obj = this;

}
總結(jié)

Java 的內(nèi)存模型是并發(fā)編程領(lǐng)域的一次重要創(chuàng)新,Happens-Before 的語義是一種因果關(guān)系。在現(xiàn)實世界里,如果 A 事件是導(dǎo)致 B 事件的起因,那么 A 事件一定是先于(Happens-Before)B 事件發(fā)生的,這個就是 Happens-Before 語義的現(xiàn)實理解。

在 Java 語言里面,Happens-Before 的語義本質(zhì)上是一種可見性,A Happens-Before B 意味著 A 事件對 B 事件來說是可見的,無論 A 事件和 B 事件是否發(fā)生在同一個線程里。例如 A 事件發(fā)生在線程 1 上,B 事件發(fā)生在線程 2 上,Happens-Before 規(guī)則保證線程 2 上也能看到 A 事件的發(fā)生。

Java 內(nèi)存模型主要分為兩部分,一部分面向你我這種編寫并發(fā)程序的應(yīng)用開發(fā)人員,另一部分是面向 JVM 的實現(xiàn)人員的,我們可以重點關(guān)注前者,也就是和編寫并發(fā)程序相關(guān)的部分,這部分內(nèi)容的核心就是 Happens-Before 規(guī)則。

參考:
Java內(nèi)存模型

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/73758.html

相關(guān)文章

  • [Java并發(fā)-3]Java互斥鎖,解決原子問題

    摘要:同一時刻只有一個線程執(zhí)行這個條件非常重要,我們稱之為互斥。那對于像轉(zhuǎn)賬這種有關(guān)聯(lián)關(guān)系的操作,我們應(yīng)該怎么去解決呢先把這個問題代碼化。 在前面的分享中我們提到。 一個或者多個操作在 CPU 執(zhí)行的過程中不被中斷的特性,稱為原子性 思考:在32位的機器上對long型變量進行加減操作存在并發(fā)問題,什么原因?。?原子性問題如何解決 我們已經(jīng)知道原子性問題是線程切換,而操作系統(tǒng)做線程切換是依賴 ...

    makeFoxPlay 評論0 收藏0
  • 來,了解一下Java內(nèi)存模型(JMM)

    摘要:因為管理人員是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場景下會存在緩存一致性問題。有沒有發(fā)現(xiàn),緩存一致性問題其實就是可見性問題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機》和《Java并發(fā)編程的藝術(shù)》等書中也都有關(guān)于這個知識點的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說自己更懵了。本文,就來整體的...

    kviccn 評論0 收藏0
  • 來,了解一下Java內(nèi)存模型(JMM)

    摘要:因為管理人員是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場景下會存在緩存一致性問題。有沒有發(fā)現(xiàn),緩存一致性問題其實就是可見性問題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機》和《Java并發(fā)編程的藝術(shù)》等書中也都有關(guān)于這個知識點的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說自己更懵了。本文,就來整體的...

    eccozhou 評論0 收藏0
  • 掌握Java內(nèi)存模型,你就是解決并發(fā)問題最靚

    摘要:掌握的內(nèi)存模型,你就是解決并發(fā)問題最靚的仔編譯優(yōu)化說的具體一些,這些方法包括和關(guān)鍵字,以及內(nèi)存模型中的規(guī)則。掌握的內(nèi)存模型,你就是解決并發(fā)問題最靚的仔共享變量藍(lán)色的虛線箭頭代表禁用了緩存,黑色的實線箭頭代表直接從主內(nèi)存中讀寫數(shù)據(jù)。 摘要:如果編寫的并發(fā)程序出現(xiàn)問題時,很難通過調(diào)試來解決相應(yīng)的問題,此時,需要一行行的檢查代碼...

    番茄西紅柿 評論0 收藏2637

發(fā)表評論

0條評論

閱讀需要支付1元查看
<