91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

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

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

Happens-before的作用是什么

發布時間:2021-06-23 11:04:26 來源:億速云 閱讀:196 作者:chen 欄目:大數據

這篇文章主要介紹“Happens-before的作用是什么”,在日常操作中,相信很多人在Happens-before的作用是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Happens-before的作用是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

寫在前面

上一篇文章并發 Bug 之源有三,請睜大眼睛看清它們 談到了可見性/原子性/有序性三個問題,這些問題通常違背我們的直覺和思考模式,也就導致了很多并發 Bug

  • 為了解決 CPU,內存,IO 的短板,增加了緩存,但這導致了可見性問題

  • 編譯器/處理器擅自優化 ( Java代碼在編譯后會變成 Java 字節碼, 字節碼被類加載器加載到 JVM 里, JVM 執行字節碼, 最終需要轉化為匯編指令在 CPU 上執行) ,導致有序性問題

初衷是好的,但引發了新問題,最有效的辦法就禁止緩存和編譯優化,問題雖然能解決,但「又回到最初的起點,呆呆地站在鏡子前」是很尷尬的,我們程序的性能就堪憂了.

解決方案

  1. 作為我們程序猿不想寫出 bug 影響 KPI,所以希望內存模型易于理解、易于編程。這就需要基于一個強內存模型來編寫代碼

  2. 作為編譯器和處理器不想讓外人說它處理速度很慢,所以希望內存模型對他們束縛越少越好,可以由他們擅自優化,這就需要基于一個弱內存模型

俗話說:「沒有什么事是開會解決不了的,如果有,那就再開一次」????

JSR-133 的專家們就有了新想法,既然不能完全禁止緩存和編譯優化,那就按需禁用緩存和編譯優化,按需就是要加一些約束,約束中就包括了上一篇文章簡單提到過的 volatile,synchronized,final 三個關鍵字,同時還有你可能聽過的 Happens-Before 原則(包含可見性和有序性的約束),Happens-before 規則也是本章的主要內容

為了滿足二者的強烈需求,照顧到雙方的情緒,于是乎: JMM 就對程序猿說了一個善意的謊言: 「會嚴格遵守 Happpen-Befores 規則,不會重排序」讓程序猿放心,私下卻有自己的策略:

  1. 對于會改變程序執行結果的重排序,JMM要求編譯器和處理器必須禁止這種重排序。

  2. 對于不會改變程序執行結果的重排序, JMM對編譯器和處理器不做要求 (JMM允許這種重排序)。

我們來用個圖說明一下:

Happens-before的作用是什么

這就是那個善意的謊言,雖是謊言,但還是照顧到了程序猿的利益,所以我們只需要了解 happens-before 規則就能得到保證 (圖畫了好久,不知道是否說明了謊言的所在????,歡迎留言)

Happens-before

Happens-before 規則主要用來約束兩個操作,兩個操作之間具有 happens-before 關系, 并不意味著前一個操作必須要在后一個操作之前執行,happens-before 僅僅要求前一個操作(執行的結果)對后一個操作可見, (the first is visible to and ordered before the second)

說了這么多,先來看一小段代碼帶你逐步走進 Happen-Befores 原則,看看是怎樣用該原則解決 可見性有序性 的問題:

class ReorderExample {
  int x = 0;
  boolean flag = false;
  public void writer() {
    x = 42;    //1
    flag = true;    //2
  }
  public void reader() {
    if (flag) { //3
      System.out.println(x);    //4
    }
  }
}

假設 A 線程執行 writer 方法,B 線程執行 reader 方法,打印出來的 x 可能會是 0,上一篇文章說明過: 因為代碼 1 和 2 沒有數據依賴關系,所以可能被重排序

flag = true;    //2
x = 42;    //1

所以,線程 A 將 flag = true 寫入但沒有為 x 重新賦值時,線程 B 可能就已經打印了 x 是 0

那么為 flag 加上 volatile 關鍵字試一下:

volatile boolean flag = false;

即便加上了 volatile 關鍵字,這個問題在 java1.5 之前還是沒有解決,但 java1.5 和其之后的版本對 volatile 語義做了增強,問題得以解決,這就離不開 Happens-before 規則的約束了,總共有 6 個規則,且看

程序順序性規則

一個線程中的每個操作, happens-before 于該線程中的任意后續操作 第一感覺這個原則是一個在理想狀態下的"廢話",并且和上面提到的會出現重排序的情況是矛盾的,注意這里是一個線程中的操作,其實隱含了「as-if-serial」語義: 說白了就是只要執行結果不被改變,無論怎么"排序",都是對的

這個規則是一個基礎規則,happens-before 是多線程的規則,所以要和其他規則約束在一起才能體現出它的順序性,別著急,繼續向下看

volatile變量規則

對一個 volatile 域的寫, happens-before 于任意后續對這個 volatile 域的讀

我將上面的程序添加兩行代碼作說明:

public class ReorderExample {

	private int x = 0;
	private int y = 1;
	private volatile boolean flag = false;

	public void writer(){
		x = 42;	//1
		y = 50;	//2
		flag = true;	//3
	}

	public void reader(){
		if (flag){	//4
			System.out.println("x:" + x);	//5
			System.out.println("y:" + y);	//6
		}
	}
}

這里涉及到了 volatile 的內存增強語義,先來看個表格:

能否重排序第二個操作第二個操作第二個操作
第一個操作普通讀/寫volatile 讀volatile 寫
普通讀/寫--NO
volatile 讀NONONO
volatile 寫-NONO

從這個表格 最后一列 可以看出:

如果第二個操作為 volatile 寫,不管第一個操作是什么,都不能重排序,這就確保了 volatile 寫之前的操作不會被重排序到 volatile 寫之后 拿上面的代碼來說,代碼 1 和 2 不會被重排序到代碼 3 的后面,但代碼 1 和 2 可能被重排序 (沒有依賴也不會影響到執行結果),說到這里和 程序順序性規則是不是就已經關聯起來了呢?

從這個表格的 倒數第二行 可以看出:

如果第一個操作為 volatile 讀,不管第二個操作是什么,都不能重排序,這確保了 volatile 讀之后的操作不會被重排序到 volatile 讀之前 拿上面的代碼來說,代碼 4 是讀取 volatile 變量,代碼 5 和 6 不會被重排序到代碼 4 之前

volatile 內存語義的實現是應用到了 「內存屏障」,因為這完全夠單獨寫一章的內容,這里為了不掩蓋主角 Happens-before 的光環,保持理解 Happens-before 的連續性,先不做過多說明

到這里,看這個規則,貌似也沒解決啥問題,因為它還要聯合第三個規則才起作用

傳遞性規則

如果 A happens-before B, 且 B happens-before C, 那么 A happens-before C 直接上圖說明一下上面的例子

Happens-before的作用是什么

從上圖可以看出

  • x =42y = 50 Happens-before flag = true, 這是規則 1

  • 寫變量(代碼 3) flag=true Happens-before 讀變量(代碼 4) if(flag),這是規則 2

根據規則 3傳遞性規則,x =42 Happens-before 讀變量 if(flag)

謎案要揭曉了: 如果線程 B 讀到了 flag 是 true,那么 x =42y = 50 對線程 B 就一定可見了,這就是 Java1.5 的增強 (之前版本是可以普通變量寫和 volatile 變量寫的重排序的)

通常上面三個規則是一種聯合約束,到這里你懂了嗎?規則還沒完,繼續看

監視器鎖規則

對一個鎖的解鎖 happens-before 于隨后對這個鎖的加鎖

這個規則我覺得你應該最熟悉了,就是解釋 synchronized 關鍵字的,來看

public class SynchronizedExample {
	private int x = 0;

	public void synBlock(){
		// 1.加鎖
		synchronized (SynchronizedExample.class){
			x = 1; // 對x賦值
		}
		// 3.解鎖
	}

	// 1.加鎖
	public synchronized void synMethod(){
		x = 2; // 對x賦值
	}
	// 3. 解鎖
}

先獲取鎖的線程,對 x 賦值之后釋放鎖,另外一個再獲取鎖,一定能看到對 x 賦值的改動,就是這么簡單,請小伙伴用下面命令查看上面程序,看同步塊和同步方法被轉換成匯編指令有何不同?

javap -c -v SynchronizedExample

這和 synchronized 的語義相關,小伙伴可以先自行了解一下,鎖的內容時會做詳細說明

start()規則

如果線程 A 執行操作 ThreadB.start() (啟動線程B), 那么 A 線程的 ThreadB.start() 操作 happens-before 于線程 B 中的任意操作,也就是說,主線程 A 啟動子線程 B 后,子線程 B 能看到主線程在啟動子線程 B 前的操作,看個程序就秒懂了

public class StartExample {
	private int x = 0;
	private int y = 1;
	private boolean flag = false;

	public static void main(String[] args) throws InterruptedException {
		StartExample startExample = new StartExample();

		Thread thread1 = new Thread(startExample::writer, "線程1");
		startExample.x = 10;
		startExample.y = 20;
		startExample.flag = true;

		thread1.start();

		System.out.println("主線程結束");
	}

	public void writer(){
		System.out.println("x:" + x );
		System.out.println("y:" + y );
		System.out.println("flag:" + flag );
	}
}

運行結果:

主線程結束
x:10
y:20
flag:true

Process finished with exit code 0

線程 1 看到了主線程調用 thread1.start() 之前的所有賦值結果,這里沒有打印「主線程結束」,你知道為什么嗎?這個守護線程知識有關系

join()規則

如果線程 A 執行操作 ThreadB.join() 并成功返回, 那么線程 B 中的任意操作 happens-before 于線程 A 從 ThreadB.join() 操作成功返回,和 start 規則剛好相反,主線程 A 等待子線程 B 完成,當子線程 B 完成后,主線程能夠看到子線程 B 的賦值操作,將程序做個小改動,你也會秒懂的

public class JoinExample {
	private int x = 0;
	private int y = 1;
	private boolean flag = false;

	public static void main(String[] args) throws InterruptedException {
		JoinExample joinExample = new JoinExample();

		Thread thread1 = new Thread(joinExample::writer, "線程1");
		thread1.start();

		thread1.join();

		System.out.println("x:" + joinExample.x );
		System.out.println("y:" + joinExample.y );
		System.out.println("flag:" + joinExample.flag );
		System.out.println("主線程結束");
	}

	public void writer(){
		this.x = 100;
		this.y = 200;
		this.flag = true;
	}
}

運行結果:

x:100
y:200
flag:true
主線程結束

Process finished with exit code 0

「主線程結束」這幾個字打印出來嘍,依舊和線程何時退出有關系

總結

  1. Happens-before 重點是解決前一個操作結果對后一個操作可見,相信到這里,你已經對 Happens-before 規則有所了解,這些規則解決了多線程編程的可見性與有序性問題,但還沒有完全解決原子性問題(除了 synchronized)

  2. start 和 join 規則也是解決主線程與子線程通信的方式之一

  3. 從內存語義的角度來說, volatile 的寫-讀與鎖的釋放-獲取有相同的內存效果;volatile 寫和鎖的釋放有相同的內存語義; volatile 讀與鎖的獲取有相同的內存語義,??????(敲黑板了) volatile 解決的是可見性問題,synchronized 解決的是原子性問題,這絕對不是一回事,后續文章也會說明

靈魂追問

  1. 同步塊和同步方法在編譯成 CPU 指令后有什么不同?

  2. 線程有 Daemon(守護線程)和非 Daemon 線程,你知道線程的退出策略嗎?

  3. 關于 Happens-before 你還有哪些疑惑呢?

到此,關于“Happens-before的作用是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

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

AI

洞头县| 莫力| 榆社县| 当雄县| 嘉兴市| 怀仁县| 谷城县| 许昌县| 乌恰县| 华坪县| 安塞县| 湄潭县| 桓台县| 上思县| 旌德县| 邢台县| 洞口县| 敖汉旗| 宣汉县| 霍林郭勒市| 兴国县| 巩留县| 江山市| 武威市| 阿拉善左旗| 潮州市| 蒲城县| 凉山| 南靖县| 鲁山县| 青铜峡市| 锡林郭勒盟| 丹江口市| 扶沟县| 三穗县| 博罗县| 鹤庆县| 聂拉木县| 大埔区| 冕宁县| 马山县|