日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線(xiàn)溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
Happens-before的作用是什么

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

創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),太倉(cāng)企業(yè)網(wǎng)站建設(shè),太倉(cāng)品牌網(wǎng)站建設(shè),網(wǎng)站定制,太倉(cāng)網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷(xiāo),網(wǎng)絡(luò)優(yōu)化,太倉(cāng)網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M(mǎn)足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專(zhuān)業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶(hù)成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

寫(xiě)在前面

上一篇文章并發(fā) Bug 之源有三,請(qǐng)睜大眼睛看清它們 談到了可見(jiàn)性/原子性/有序性三個(gè)問(wèn)題,這些問(wèn)題通常違背我們的直覺(jué)和思考模式,也就導(dǎo)致了很多并發(fā) Bug

  • 為了解決 CPU,內(nèi)存,IO 的短板,增加了緩存,但這導(dǎo)致了可見(jiàn)性問(wèn)題

  • 編譯器/處理器擅自優(yōu)化 ( Java代碼在編譯后會(huì)變成 Java 字節(jié)碼, 字節(jié)碼被類(lèi)加載器加載到 JVM 里, JVM 執(zhí)行字節(jié)碼, 最終需要轉(zhuǎn)化為匯編指令在 CPU 上執(zhí)行) ,導(dǎo)致有序性問(wèn)題

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

解決方案

  1. 作為我們程序猿不想寫(xiě)出 bug 影響 KPI,所以希望內(nèi)存模型易于理解、易于編程。這就需要基于一個(gè)強(qiáng)內(nèi)存模型來(lái)編寫(xiě)代碼

  2. 作為編譯器和處理器不想讓外人說(shuō)它處理速度很慢,所以希望內(nèi)存模型對(duì)他們束縛越少越好,可以由他們擅自?xún)?yōu)化,這就需要基于一個(gè)弱內(nèi)存模型

俗話(huà)說(shuō):「沒(méi)有什么事是開(kāi)會(huì)解決不了的,如果有,那就再開(kāi)一次」????

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

為了滿(mǎn)足二者的強(qiáng)烈需求,照顧到雙方的情緒,于是乎: JMM 就對(duì)程序猿說(shuō)了一個(gè)善意的謊言: 「會(huì)嚴(yán)格遵守 Happpen-Befores 規(guī)則,不會(huì)重排序」讓程序猿放心,私下卻有自己的策略:

  1. 對(duì)于會(huì)改變程序執(zhí)行結(jié)果的重排序,JMM要求編譯器和處理器必須禁止這種重排序。

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

我們來(lái)用個(gè)圖說(shuō)明一下:

Happens-before的作用是什么

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

Happens-before

Happens-before 規(guī)則主要用來(lái)約束兩個(gè)操作,兩個(gè)操作之間具有 happens-before 關(guān)系, 并不意味著前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行,happens-before 僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對(duì)后一個(gè)操作可見(jiàn), (the first is visibleto and ordered before the second)

說(shuō)了這么多,先來(lái)看一小段代碼帶你逐步走進(jìn) Happen-Befores 原則,看看是怎樣用該原則解決 可見(jiàn)性有序性的問(wèn)題:

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
    }
  }
}

假設(shè) A 線(xiàn)程執(zhí)行 writer 方法,B 線(xiàn)程執(zhí)行 reader 方法,打印出來(lái)的 x 可能會(huì)是 0,上一篇文章說(shuō)明過(guò): 因?yàn)榇a 1 和 2 沒(méi)有數(shù)據(jù)依賴(lài)關(guān)系,所以可能被重排序

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

所以,線(xiàn)程 A 將 flag = true 寫(xiě)入但沒(méi)有為 x 重新賦值時(shí),線(xiàn)程 B 可能就已經(jīng)打印了 x 是 0

那么為 flag 加上 volatile 關(guān)鍵字試一下:

volatile boolean flag = false;

即便加上了 volatile 關(guān)鍵字,這個(gè)問(wèn)題在 java1.5 之前還是沒(méi)有解決,但 java1.5 和其之后的版本對(duì) volatile 語(yǔ)義做了增強(qiáng),問(wèn)題得以解決,這就離不開(kāi) Happens-before 規(guī)則的約束了,總共有 6 個(gè)規(guī)則,且看

程序順序性規(guī)則

一個(gè)線(xiàn)程中的每個(gè)操作, happens-before 于該線(xiàn)程中的任意后續(xù)操作 第一感覺(jué)這個(gè)原則是一個(gè)在理想狀態(tài)下的"廢話(huà)",并且和上面提到的會(huì)出現(xiàn)重排序的情況是矛盾的,注意這里是一個(gè)線(xiàn)程中的操作,其實(shí)隱含了「as-if-serial」語(yǔ)義: 說(shuō)白了就是只要執(zhí)行結(jié)果不被改變,無(wú)論怎么"排序",都是對(duì)的

這個(gè)規(guī)則是一個(gè)基礎(chǔ)規(guī)則,happens-before 是多線(xiàn)程的規(guī)則,所以要和其他規(guī)則約束在一起才能體現(xiàn)出它的順序性,別著急,繼續(xù)向下看

volatile變量規(guī)則

對(duì)一個(gè) volatile 域的寫(xiě), happens-before 于任意后續(xù)對(duì)這個(gè) volatile 域的讀

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

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 的內(nèi)存增強(qiáng)語(yǔ)義,先來(lái)看個(gè)表格:

能否重排序第二個(gè)操作第二個(gè)操作第二個(gè)操作
第一個(gè)操作普通讀/寫(xiě)volatile 讀volatile 寫(xiě)
普通讀/寫(xiě)--NO
volatile 讀NONONO
volatile 寫(xiě)-NONO

從這個(gè)表格 最后一列可以看出:

如果第二個(gè)操作為 volatile 寫(xiě),不管第一個(gè)操作是什么,都不能重排序,這就確保了 volatile 寫(xiě)之前的操作不會(huì)被重排序到 volatile 寫(xiě)之后拿上面的代碼來(lái)說(shuō),代碼 1 和 2 不會(huì)被重排序到代碼 3 的后面,但代碼 1 和 2 可能被重排序 (沒(méi)有依賴(lài)也不會(huì)影響到執(zhí)行結(jié)果),說(shuō)到這里和 程序順序性規(guī)則是不是就已經(jīng)關(guān)聯(lián)起來(lái)了呢?

從這個(gè)表格的 倒數(shù)第二行可以看出:

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

volatile 內(nèi)存語(yǔ)義的實(shí)現(xiàn)是應(yīng)用到了 「內(nèi)存屏障」,因?yàn)檫@完全夠單獨(dú)寫(xiě)一章的內(nèi)容,這里為了不掩蓋主角 Happens-before 的光環(huán),保持理解 Happens-before 的連續(xù)性,先不做過(guò)多說(shuō)明

到這里,看這個(gè)規(guī)則,貌似也沒(méi)解決啥問(wèn)題,因?yàn)樗€要聯(lián)合第三個(gè)規(guī)則才起作用

傳遞性規(guī)則

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

Happens-before的作用是什么

從上圖可以看出

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

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

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

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

通常上面三個(gè)規(guī)則是一種聯(lián)合約束,到這里你懂了嗎?規(guī)則還沒(méi)完,繼續(xù)看

監(jiān)視器鎖規(guī)則

對(duì)一個(gè)鎖的解鎖 happens-before 于隨后對(duì)這個(gè)鎖的加鎖

這個(gè)規(guī)則我覺(jué)得你應(yīng)該最熟悉了,就是解釋 synchronized 關(guān)鍵字的,來(lái)看

public class SynchronizedExample {
	private int x = 0;

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

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

先獲取鎖的線(xiàn)程,對(duì) x 賦值之后釋放鎖,另外一個(gè)再獲取鎖,一定能看到對(duì) x 賦值的改動(dòng),就是這么簡(jiǎn)單,請(qǐng)小伙伴用下面命令查看上面程序,看同步塊和同步方法被轉(zhuǎn)換成匯編指令有何不同?

javap -c -v SynchronizedExample

這和 synchronized 的語(yǔ)義相關(guān),小伙伴可以先自行了解一下,鎖的內(nèi)容時(shí)會(huì)做詳細(xì)說(shuō)明

start()規(guī)則

如果線(xiàn)程 A 執(zhí)行操作 ThreadB.start() (啟動(dòng)線(xiàn)程B), 那么 A 線(xiàn)程的 ThreadB.start() 操作 happens-before 于線(xiàn)程 B 中的任意操作,也就是說(shuō),主線(xiàn)程 A 啟動(dòng)子線(xiàn)程 B 后,子線(xiàn)程 B 能看到主線(xiàn)程在啟動(dòng)子線(xiàn)程 B 前的操作,看個(gè)程序就秒懂了

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, "線(xiàn)程1");
		startExample.x = 10;
		startExample.y = 20;
		startExample.flag = true;

		thread1.start();

		System.out.println("主線(xiàn)程結(jié)束");
	}

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

運(yùn)行結(jié)果:

主線(xiàn)程結(jié)束
x:10
y:20
flag:true

Process finished with exit code 0

線(xiàn)程 1 看到了主線(xiàn)程調(diào)用 thread1.start() 之前的所有賦值結(jié)果,這里沒(méi)有打印「主線(xiàn)程結(jié)束」,你知道為什么嗎?這個(gè)守護(hù)線(xiàn)程知識(shí)有關(guān)系

join()規(guī)則

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

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, "線(xiàn)程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("主線(xiàn)程結(jié)束");
	}

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

運(yùn)行結(jié)果:

x:100
y:200
flag:true
主線(xiàn)程結(jié)束

Process finished with exit code 0

「主線(xiàn)程結(jié)束」這幾個(gè)字打印出來(lái)嘍,依舊和線(xiàn)程何時(shí)退出有關(guān)系

總結(jié)

  1. Happens-before 重點(diǎn)是解決前一個(gè)操作結(jié)果對(duì)后一個(gè)操作可見(jiàn),相信到這里,你已經(jīng)對(duì) Happens-before 規(guī)則有所了解,這些規(guī)則解決了多線(xiàn)程編程的可見(jiàn)性與有序性問(wèn)題,但還沒(méi)有完全解決原子性問(wèn)題(除了 synchronized)

  2. start 和 join 規(guī)則也是解決主線(xiàn)程與子線(xiàn)程通信的方式之一

  3. 從內(nèi)存語(yǔ)義的角度來(lái)說(shuō), volatile 的寫(xiě)-讀與鎖的釋放-獲取有相同的內(nèi)存效果;volatile 寫(xiě)和鎖的釋放有相同的內(nèi)存語(yǔ)義; volatile 讀與鎖的獲取有相同的內(nèi)存語(yǔ)義,??????(敲黑板了) volatile 解決的是可見(jiàn)性問(wèn)題,synchronized 解決的是原子性問(wèn)題,這絕對(duì)不是一回事,后續(xù)文章也會(huì)說(shuō)明

靈魂追問(wèn)

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

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

  3. 關(guān)于 Happens-before 你還有哪些疑惑呢?

到此,關(guān)于“Happens-before的作用是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!


文章題目:Happens-before的作用是什么
鏈接分享:http://www.dlmjj.cn/article/ipdpph.html