人妻少妇乱子伦精品_日韩人妻潮喷视频网站_日本最新最全无码不卡免费_日韩AV无码中文

當(dāng)前位置: 首頁 > 科技新聞 >

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使

時(shí)間:2020-06-04 17:38來源:網(wǎng)絡(luò)整理 瀏覽:
作者簡介:徐隆曦,滴滴出行高級工程師。本文選自:拉勾教育專欄《Java 并發(fā)編程 78 講》你好,我是徐隆曦,今天我們主要探討一個(gè)問題?:?

作者簡介:徐隆曦,滴滴出行高級工程師。

本文選自:拉勾教育專欄《Java 并發(fā)編程 78 講》

你好,我是徐隆曦,今天我們主要探討一個(gè)問題?:??為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

首先,我們來看第一個(gè)問題,為什么 wait 方法必須在 synchronized 保護(hù)的同步代碼中使用?

我們先來看看 wait 方法的源碼注釋是怎么寫的:

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

上面這段英文的意思是說,在使用 wait 方法時(shí),必須把 wait 方法寫在 synchronized 保護(hù)的 while 代碼塊中,并始終判斷執(zhí)行條件是否滿足,如果滿足就往下繼續(xù)執(zhí)行,如果不滿足就執(zhí)行 wait 方法,而在執(zhí)行 wait 方法之前,必須先持有對象的 monitor 鎖,也就是通常所說的 synchronized 鎖。那么設(shè)計(jì)成這樣有什么好處呢?

本文選自:拉勾教育專欄《Java 并發(fā)編程 78 講》見文末了解更多

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

我們逆向思考這個(gè)問題,如果不要求 wait 方法放在 synchronized 保護(hù)的同步代碼中使用,而是可以隨意調(diào)用,那么就有可能寫出這樣的代碼:

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

在代碼中可以看到有兩個(gè)方法,give 方法負(fù)責(zé)往 buffer 中添加數(shù)據(jù),添加完之后執(zhí)行 notify 方法來喚醒之前等待的線程,而 take 方法負(fù)責(zé)檢查整個(gè) buffer 是否為空,如果為空就進(jìn)入等待,如果不為空就取出一個(gè)數(shù)據(jù),這是典型的生產(chǎn)者消費(fèi)者的思想。

本文選自:拉勾教育專欄《Java 并發(fā)編程 78 講》見文末了解更多

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

但是這段代碼并沒有受 synchronized 保護(hù),于是便有可能發(fā)生以下場景:

首先,消費(fèi)者線程調(diào)用 take 方法并判斷 buffer.isEmpty 方法是否返回 true,若為 true 代表buffer是空的,則線程希望進(jìn)入等待,但是在線程調(diào)用 wait 方法之前,就被調(diào)度器暫停了,所以此時(shí)還沒來得及執(zhí)行 wait 方法。此時(shí)生產(chǎn)者開始運(yùn)行,執(zhí)行了整個(gè) give 方法,它往 buffer 中添加了數(shù)據(jù),并執(zhí)行了 notify 方法,但 notify 并沒有任何效果,因?yàn)橄M(fèi)者線程的 wait 方法沒來得及執(zhí)行,所以沒有線程在等待被喚醒。此時(shí),剛才被調(diào)度器暫停的消費(fèi)者線程回來繼續(xù)執(zhí)行 wait 方法并進(jìn)入了等待。為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

雖然剛才消費(fèi)者判斷了 buffer.isEmpty 條件,但真正執(zhí)行 wait 方法時(shí),之前的buffer.isEmpty 的結(jié)果已經(jīng)過期了,不再符合最新的場景了,因?yàn)檫@里的“判斷-執(zhí)行”不是一個(gè)原子操作,它在中間被打斷了,是線程不安全的。

假設(shè)這時(shí)沒有更多的生產(chǎn)者進(jìn)行生產(chǎn),消費(fèi)者便有可能陷入無窮無盡的等待,因?yàn)樗e(cuò)過了剛才 give 方法內(nèi)的 notify 的喚醒。

我們看到正是因?yàn)?wait 方法所在的 take 方法沒有被 synchronized 保護(hù),所以它的 while 判斷和 wait 方法無法構(gòu)成原子操作,那么此時(shí)整個(gè)程序就很容易出錯(cuò)。

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

我們把代碼改寫成源碼注釋所要求的被 synchronized 保護(hù)的同步代碼塊的形式,代碼如下:

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

這樣就可以確保 notify 方法永遠(yuǎn)不會(huì)在 buffer.isEmpty 和 wait 方法之間被調(diào)用,提升了程序的安全性。

另外,wait 方法會(huì)釋放monitor鎖,這也要求我們必須首先進(jìn)入到 synchronized 內(nèi)持有這把鎖。

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

這里還存在一個(gè)“虛假喚醒”(spurious wakeup)的問題,線程可能在既沒有被notify/notifyAll,也沒有被中斷或者超時(shí)的情況下被喚醒,這種喚醒是我們不希望看到的。雖然在實(shí)際生產(chǎn)中,虛假喚醒發(fā)生的概率很小,但是程序依然需要保證在發(fā)生虛假喚醒的時(shí)候的正確性,所以就需要采用while循環(huán)的結(jié)構(gòu):

為什么 wait 必須在 synchronized 保護(hù)的同步代碼中使用?

這樣即便被虛假喚醒了,也會(huì)再次檢查while里面的條件,如果不滿足條件,就會(huì)繼續(xù)wait,也就消除了虛假喚醒的風(fēng)險(xiǎn)。

好了,本次的內(nèi)容講完了,下一課時(shí)我將講解“有哪幾種實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式的方法?”記得按時(shí)來學(xué)習(xí)哦,下次見。

本文選自:拉勾教育專欄《Java 并發(fā)編程 78 講》見文末了解更多

版權(quán)聲明:本文版權(quán)歸屬拉勾教育及該專欄作者,任何媒體、網(wǎng)站或個(gè)人未經(jīng)本網(wǎng)協(xié)議授權(quán)不得轉(zhuǎn)載、鏈接、轉(zhuǎn)貼或以其他方式復(fù)制發(fā)布/發(fā)表,違者必究。

推薦內(nèi)容