国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

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

lk20150415 / 1396人閱讀

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

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

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

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

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

使用volatile的困惑

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

例如,我們聲明一個(gè)volatile變量 ,volatile int x = 0,它表達(dá)的是:告訴編譯器,對(duì)這個(gè)變量的讀寫(xiě),不能使用 CPU 緩存,必須從內(nèi)存中讀取或者寫(xiě)入。看起來(lái)語(yǔ)義很明確,實(shí)際情況比較困惑。

看下以下代碼:

class VolatileExample {

  int x = 0;

  volatile boolean v = false;

  public void writer() {

    x = 42;

    v = true;

  }

  public void reader() {

    if (v == true) {

      // 這里 x 會(huì)是多少呢?

    }

  }

}

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

Happens-Before 規(guī)則

這里直接給出定義:

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

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

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

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

2,volatile 變量規(guī)則

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

這個(gè)就有點(diǎn)費(fèi)解了,對(duì)一個(gè) volatile 變量的寫(xiě)操作相對(duì)于后續(xù)對(duì)這個(gè) volatile 變量的讀操作可見(jiàn)。

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 會(huì)是多少呢?

    }

  }

}

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

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

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

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

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

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

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

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

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

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

  if (this.x < 12) {

    this.x = 12; 

  }  

} // 此處自動(dòng)解鎖

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

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

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

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

Thread B = new Thread(()->{

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

  // 所有對(duì)共享變量的修改,此處皆可見(jiàn)

  // 此例中,var==77

});

// 此處對(duì)共享變量 var 修改

var = 77;

// 主線程啟動(dòng)子線程

B.start();

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

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

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

Thread B = new Thread(()->{

  // 此處對(duì)共享變量 var 修改

  var = 66;

});

// 例如此處對(duì)共享變量修改,

// 則這個(gè)修改結(jié)果對(duì)線程 B 可見(jiàn)

// 主線程啟動(dòng)子線程

B.start();

B.join()

// 子線程所有對(duì)共享變量的修改

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

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

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

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

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

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

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

final int x;

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

public FinalFieldExample() { 

  x = 3;

  y = 4;

  // 此處就是講 this 逸出,

  global.obj = this;

}
總結(jié)

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

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

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

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

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/73758.html

相關(guān)文章

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

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

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

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

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

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

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

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

    番茄西紅柿 評(píng)論0 收藏2637

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<