自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入理解 Happens-Before 原則

開發(fā) 前端
happens-before 指的是 Java 內(nèi)存模型中兩項操作的順序關(guān)系。例如說操作 A 先于操作 B,也就是說操作 A 發(fā)生在操作 B 之前,操作 A 產(chǎn)生的影響能夠被操作 B 觀察到。這里的「影響」包括:內(nèi)存中共享變量的值、發(fā)送了消息、調(diào)用了方法等。

今天就讓我們來深入聊聊關(guān)于 happens-before 的那些事兒!

圖片

什么是 happens-before?

happens-before 指的是 Java 內(nèi)存模型中兩項操作的順序關(guān)系。例如說操作 A 先于操作 B,也就是說操作 A 發(fā)生在操作 B 之前,操作 A 產(chǎn)生的影響能夠被操作 B 觀察到。這里的「影響」包括:內(nèi)存中共享變量的值、發(fā)送了消息、調(diào)用了方法等。

舉個很簡單的例子:下面代碼里 i=1 在線程 A 中執(zhí)行,而 j=i 在線程 B 中執(zhí)行。因為 i=1 操作先于 j=i 執(zhí)行,那么 i=1 操作的結(jié)果就應(yīng)該能夠被線程 B 觀察到。

// 在線程 A 中執(zhí)行
i = 1;
// 在線程 B 中執(zhí)行
j = i;

Java 內(nèi)存模型下一共有 8 條 happens-before 規(guī)則,如果線程間的操作無法從如下幾個規(guī)則推導(dǎo)出來,那么它們的操作就沒有順序性保障,虛擬機或者操作系統(tǒng)就能隨意地進(jìn)行重排序,從而可能會發(fā)生并發(fā)安全問題。

  • 程序次序規(guī)則(Program Order Rule):在一個線程內(nèi),按照程序代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作。準(zhǔn)確地說,應(yīng)該是控制流順序而不是程序代碼順序,因為要考慮分支、循環(huán)等結(jié)構(gòu)。
  • 管程鎖定規(guī)則(Monitor Lock Rule):一個 unlock 操作先行發(fā)生于后面對同一個鎖的 lock 操作。這里必須強調(diào)的是同一個鎖,而 “后面” 是指時間上的先后順序。
  • volatile 變量規(guī)則(Volatile Variable Rule):對一個 volatile 變量的寫操作先行發(fā)生于后面對這個變量的讀操作,這里的 “后面” 同樣是指時間上的先后順序。
  • 線程啟動規(guī)則(Thread Start Rule):Thread 對象的 start () 方法先行發(fā)生于此線程的每一個動作。
  • 線程終止規(guī)則(Thread Termination Rule):線程中的所有操作都先行發(fā)生于對此線程的終止檢測,我們可以通過 Thread.join () 方法結(jié)束、Thread.isAlive () 的返回值等手段檢測到線程已經(jīng)終止執(zhí)行。
  • 線程中斷規(guī)則(Thread Interruption Rule):對線程 interrupt () 方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生,可以通過 Thread.interrupted () 方法檢測到是否有中斷發(fā)生。
  • 對象終結(jié)規(guī)則(Finalizer Rule):一個對象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的 finalize () 方法的開始。
  • 傳遞性(Transitivity):如果操作 A 先行發(fā)生于操作 B,操作 B 先行發(fā)生于操作 C,那就可以得出操作 A 先行發(fā)生于操作 C 的結(jié)論。

極簡實踐案例

Java 語言無須任何同步手段保障,就能成立的先行發(fā)生規(guī)則,就只有上面這些了。下面舉個例子來說明如何用這些規(guī)則去判斷操作是否具備順序性,是否是線程安全的。

private int value = 0;

public void setValue(int value){
this.value = value;
}

public int getValue(){
return value;
}

上面的代碼是一組很普通的 getter/setter 方法。假設(shè)線程 A 和 B,線程 A 先(時間上的先后)調(diào)用了 setValue(1),之后線程 B 調(diào)用了同一個對象的 getValue(),那么線程 B 收到的返回值是什么?

我們依次分析一下先行發(fā)生原則中的各項規(guī)則:

  • 首先,由于兩個方法分別由線程 A 和線程 B 調(diào)用,不在一個線程中,所以程序次序規(guī)則在這里不適用。
  • 接著,由于沒有同步塊,自然就不會發(fā)生 lock 和 unlock 操作,所以管程鎖定規(guī)則不適用。
  • 繼續(xù),由于 value 變量沒有被 volatile 關(guān)鍵字修飾,所以 volatile 變量規(guī)則不適用。
  • 繼續(xù),后面的線程啟動、終止、中斷規(guī)則和對象終結(jié)規(guī)則也和這里完全沒有關(guān)系。
  • 最后,因為沒有一個適用的先行發(fā)生規(guī)則,所以最后一條傳遞性也無從談起。

因此,即使我們知道線程 A 在操作時間上先于線程 B,但我們還是無法確定線程 B getValue() 方法的返回結(jié)果。換句話說,這里面的操作不是線程安全的。

那怎么修復(fù)這個問題呢?

我們至少有兩種比較簡單的方案可以選擇:

  • 第一種,要么把getter/setter? 方法都定義為synchronized 方法,這樣就可以套用管程鎖定規(guī)則。
  • 第二種,要么把value? 定義為volatile? 變量,由于setter? 方法對value? 的修改不依賴value? 的原值,滿足volatile? 關(guān)鍵字使用場景,這樣就可以套用volatile 變量規(guī)則來實現(xiàn)先行發(fā)生關(guān)系。

通過上面這個案例,我們知道:一個操作時間上線發(fā)生,不代表這個操作會「先行發(fā)生」。 那如果一個操作「先行發(fā)生」,是否就能推導(dǎo)出這個操作必定是時間上先發(fā)生呢?其實并不能,因為有可能發(fā)生指令重排序。

// 如下操作在同一個線程中執(zhí)行
int j = 1;
int j = 2;

上述代碼在同一線程中執(zhí)行,根據(jù)程序執(zhí)行次序規(guī)則,int i = 1; 的操作先行發(fā)生于 int j = 2;,但 int j =2 的代碼有可能被處理器先執(zhí)行,因為它們不相互依賴,不影響先行發(fā)生原則的正確性。

上述這兩個案例綜合起來證明了一個結(jié)論:時間先后順序與先行發(fā)生原則之間基本沒有太大的關(guān)系,所以我們衡量并發(fā)安全問題的時候不要受到時間順序的干擾,一切必須以先行發(fā)生原則為準(zhǔn)。

總結(jié)

happens-before 原則一共有 8 條原則,它是對 Java 內(nèi)存模型規(guī)則的簡化,可以幫助編程人員提高編程效率。

時間先后順序與先行發(fā)生原則之間基本沒有太大的關(guān)系,我們衡量并發(fā)安全問題的時候不要受到時間順序的干擾,一切必須以先行發(fā)生原則為準(zhǔn)。

圖片

深入理解 happens-before 原則

責(zé)任編輯:武曉燕 來源: 陳樹義
相關(guān)推薦

2021-05-09 18:32:05

JMMHappens-befJava

2020-05-28 07:50:18

重排序happens-befCPU

2022-06-08 13:54:23

指令重排Java

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2020-07-21 08:26:08

SpringSecurity過濾器

2021-07-29 07:51:43

工具 HappensBefore

2013-09-22 14:57:19

AtWood

2009-09-25 09:14:35

Hibernate日志

2023-10-19 11:12:15

Netty代碼

2021-02-17 11:25:33

前端JavaScriptthis

2020-09-23 10:00:26

Redis數(shù)據(jù)庫命令

2017-01-10 08:48:21

2017-08-15 13:05:58

Serverless架構(gòu)開發(fā)運維

2019-06-25 10:32:19

UDP編程通信

2024-02-21 21:14:20

編程語言開發(fā)Golang

2021-08-11 11:25:22

happens - bJava代碼

2022-11-04 09:43:05

Java線程

2015-11-04 09:57:18

JavaScript原型

2021-05-13 21:27:24

ThreadLocal多線程多線程并發(fā)安全
點贊
收藏

51CTO技術(shù)棧公眾號