国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

線程與鎖

共 28434字,需瀏覽 57分鐘

 ·

2021-10-25 22:33

點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)??

在 java 并發(fā)編程中,線程和鎖永遠(yuǎn)是最重要的概念。語言規(guī)范雖然是規(guī)范描述,但是其中也有非常多的知識(shí)和最佳實(shí)踐是值得學(xué)習(xí)的,相信這篇文章還是可以給很多讀者提供學(xué)習(xí)參考的。

本文主要是翻譯 + 解釋?Oracle?《The Java Language Specification, Java SE 8 Edition》?的第17章?《Threads and Locks》?,原文大概30頁pdf,我加入了很多自己的理解,希望能幫大家把規(guī)范看懂,并且從中得到很多你一直想要知道但是還不知道的知識(shí)。

注意,本文在說 Java 語言規(guī)范,不是 JVM 規(guī)范,JVM 的實(shí)現(xiàn)需要滿足語言規(guī)范中定義的內(nèi)容,但是具體的實(shí)現(xiàn)細(xì)節(jié)由各 JVM 廠商自己來決定。所以,語言規(guī)范要盡可能嚴(yán)謹(jǐn)全面,但是也不能限制過多,不然會(huì)限制 JVM 廠商對(duì)很多細(xì)節(jié)進(jìn)行性能優(yōu)化。

我能力有限,雖然已經(jīng)很用心了,但有些地方我真的不懂,我已經(jīng)在文中標(biāo)記出來了。

建議分 3 部分閱讀。

  • 將 17.1、17.2、17.3 一起閱讀,這里關(guān)于線程中的 wait、notify、中斷有很多的知識(shí);
  • 17.4 的內(nèi)存模型比較長(zhǎng),重排序和 happens-before 關(guān)系是重點(diǎn);
  • 剩下的 final、字分裂、double和long的非原子問題,這些都是相對(duì)獨(dú)立的 topic。

Chapter 17. Threads and Locks

前言

在 java 中,線程由 Thread 類表示,用戶創(chuàng)建線程的唯一方式是創(chuàng)建 Thread 類的一個(gè)實(shí)例,每一個(gè)線程都和這樣的一個(gè)實(shí)例關(guān)聯(lián)。在相應(yīng)的 Thread 實(shí)例上調(diào)用 start() 方法將啟動(dòng)一個(gè)線程。

如果沒有正確使用同步,線程表現(xiàn)出來的現(xiàn)象將會(huì)是令人疑惑的、違反直覺的。這個(gè)章節(jié)將描述多線程編程的語義問題,包括一系列的規(guī)則,這些規(guī)則定義了在多線程環(huán)境中線程對(duì)共享內(nèi)存中值的修改是否對(duì)其他線程立即可見。

java編程語言內(nèi)存模型定義了統(tǒng)一的內(nèi)存模型用于屏蔽不同的硬件架構(gòu),在沒有歧義的情況下,下面將用內(nèi)存模型表示這個(gè)概念。

這些語義沒有規(guī)定多線程的程序在 JVM 的實(shí)現(xiàn)上應(yīng)該怎么執(zhí)行,而是限定了一系列規(guī)則,由 JVM 廠商來滿足這些規(guī)則,即不管 JVM 的執(zhí)行策略是什么,表現(xiàn)出來的行為必須是可被接受的。

操作系統(tǒng)有自己的內(nèi)存模型,C/C++ 這些語言直接使用的就是操作系統(tǒng)的內(nèi)存模型,而 Java 為了屏蔽各個(gè)系統(tǒng)的差異,定義了自己的統(tǒng)一的內(nèi)存模型。簡(jiǎn)單說,Java 開發(fā)者不再關(guān)心每個(gè) CPU 核心有自己的內(nèi)存,然后共享主內(nèi)存。而是把關(guān)注點(diǎn)轉(zhuǎn)移到:每個(gè)線程都有自己的工作內(nèi)存,所有線程共享主內(nèi)存。

17.1 同步(synchronization)

Java 提供了多種線程之間通信的機(jī)制,其中最基本的就是使用同步 (synchronization),其使用監(jiān)視器 (monitor) 來實(shí)現(xiàn)。java中的每個(gè)對(duì)象都關(guān)聯(lián)了一個(gè)監(jiān)視器,線程可以對(duì)其進(jìn)行加鎖和解鎖操作。

在同一時(shí)間,只有一個(gè)線程可以拿到對(duì)象上的監(jiān)視器鎖。如果其他線程在鎖被占用期間試圖去獲取鎖,那么將會(huì)被阻塞直到成功獲取到鎖。同時(shí),監(jiān)視器鎖可以重入,也就是說如果線程 t 拿到了鎖,那么線程 t 可以在解鎖之前重復(fù)獲取鎖;每次解鎖操作會(huì)反轉(zhuǎn)一次加鎖產(chǎn)生的效果。

synchronized 有以下兩種使用方式:

  • synchronized 代碼塊。synchronized(object)在對(duì)某個(gè)對(duì)象上執(zhí)行加鎖時(shí),會(huì)嘗試在該對(duì)象的監(jiān)視器上進(jìn)行加鎖操作,只有成功獲取鎖之后,線程才會(huì)繼續(xù)往下執(zhí)行。線程獲取到了監(jiān)視器鎖后,將繼續(xù)執(zhí)行synchronized 代碼塊中的代碼,如果代碼塊執(zhí)行完成,或者拋出了異常,線程將會(huì)自動(dòng)對(duì)該對(duì)象上的監(jiān)視器執(zhí)行解鎖操作。
  • synchronized 作用于方法,稱為同步方法。同步方法被調(diào)用時(shí),會(huì)自動(dòng)執(zhí)行加鎖操作,只有加鎖成功,方法體才會(huì)得到執(zhí)行。如果被 synchronized 修飾的方法是實(shí)例方法,那么這個(gè)實(shí)例的監(jiān)視器會(huì)被鎖定。如果是 static 方法,線程會(huì)鎖住相應(yīng)的?Class 對(duì)象的監(jiān)視器。方法體執(zhí)行完成或者異常退出后,會(huì)自動(dòng)執(zhí)行解鎖操作。

Java語言規(guī)范既不要求阻止死鎖的發(fā)生,也不要求檢測(cè)到死鎖的發(fā)生。如果線程要在多個(gè)對(duì)象上執(zhí)行加鎖操作,那么就應(yīng)該使用傳統(tǒng)的方法來避免死鎖的發(fā)生,如果有必要的話,需要?jiǎng)?chuàng)建更高層次的不會(huì)產(chǎn)生死鎖的加鎖原語。

java 還提供了其他的一些同步機(jī)制,比如對(duì) volatile 變量的讀寫、使用 java.util.concurrent 包中的同步工具類等。

同步這一節(jié)說了 Java 并發(fā)編程中最基礎(chǔ)的 synchronized 這個(gè)關(guān)鍵字,大家一定要理解 synchronize 的鎖是什么,它的鎖是基于 Java 對(duì)象的監(jiān)視器 monitor,所以任何對(duì)象都可以用來做鎖。有興趣的讀者可以去了解相關(guān)知識(shí),包括偏向鎖、輕量級(jí)鎖、重量級(jí)鎖等。

小知識(shí)點(diǎn):對(duì) Class 對(duì)象加鎖、對(duì)對(duì)象加鎖,它們之間不構(gòu)成同步。synchronized 作用于靜態(tài)方法時(shí)是對(duì)?Class 對(duì)象加鎖,作用于實(shí)例方法時(shí)是對(duì)實(shí)例加鎖。

面試中經(jīng)常會(huì)問到一個(gè)類中的兩個(gè) synchronized static 方法之間是否構(gòu)成同步?構(gòu)成同步。

17.2 等待集合 和 喚醒(Wait Sets and Notification)

每個(gè) java 對(duì)象,都關(guān)聯(lián)了一個(gè)監(jiān)視器,也關(guān)聯(lián)了一個(gè)等待集合。等待集合是一個(gè)線程集合。

當(dāng)對(duì)象被創(chuàng)建出來時(shí),它的等待集合是空的,對(duì)于向等待集合中添加或者移除線程的操作都是原子的,以下幾個(gè)操作可以操縱這個(gè)等待集合:Object.wait, Object.notify, Object.notifyAll。

等待集合也可能受到線程的中斷狀態(tài)的影響,也受到線程中處理中斷的方法的影響。另外,sleep 方法和 join 方法可以感知到線程的 wait 和 notify。

這里概括得比較簡(jiǎn)略,沒看懂的讀者沒關(guān)系,繼續(xù)往下看就是了。

這節(jié)要講Java線程的相關(guān)知識(shí),主要包括:

  • Thread 中的 sleep、join、interrupt
  • 繼承自 Object 的 wait、notify、notifyAll
  • 還有 Java 的中斷,這個(gè)概念也很重要

17.2.1 等待 (Wait)

等待操作由以下幾個(gè)方法引發(fā):wait(),wait(long millisecs),wait(long millisecs, int nanosecs)。在后面兩個(gè)重載方法中,如果參數(shù)為 0,即 wait(0)、wait(0, 0) 和 wait() 是等效的。

如果調(diào)用 wait 方法時(shí)沒有拋出 InterruptedException 異常,則表示正常返回。

前方高能,請(qǐng)讀者保持高度精神集中。

我們?cè)诰€程 t 中對(duì)對(duì)象 m 調(diào)用 m.wait() 方法,n 代表加鎖編號(hào),同時(shí)還沒有相匹配的解鎖操作,則下面的其中之一會(huì)發(fā)生:

  • 如果 n 等于 0(如線程 t 沒有持有對(duì)象 m 的鎖),那么會(huì)拋出 IllegalMonitorStateException 異常。

注意,如果沒有獲取到監(jiān)視器鎖,wait 方法是會(huì)拋異常的,而且注意這個(gè)異常是IllegalMonitorStateException異常。這是重要知識(shí)點(diǎn),要考。

  • 如果線程 t 調(diào)用的是 m.wait(millisecs) 或m.wait(millisecs, nanosecs),形參millisecs 不能為負(fù)數(shù),nanosecs 取值應(yīng)為 [0, 999999],否則會(huì)拋出IllegalArgumentException 異常。
  • 如果線程 t 被中斷,此時(shí)中斷狀態(tài)為 true,則 wait 方法將拋出 InterruptedException 異常,并將中斷狀態(tài)設(shè)置為 false。

中斷,如果讀者不了解這個(gè)概念,可以參考我在 AQS(二) 中的介紹,這是非常重要的知識(shí)。

  • 否則,下面的操作會(huì)順序發(fā)生:

注意:到這里的時(shí)候,wait 參數(shù)是正常的,同時(shí) t 沒有被中斷,并且線程 t 已經(jīng)拿到了 m 的監(jiān)視器鎖。

1.線程 t 會(huì)加入到對(duì)象 m 的等待集合中,執(zhí)行 加鎖編號(hào) n 對(duì)應(yīng)的解鎖操作

這里也非常關(guān)鍵,前面說了,wait 方法的調(diào)用必須是線程獲取到了對(duì)象的監(jiān)視器鎖,而到這里會(huì)進(jìn)行解鎖操作。切記切記。。。

?public?Object?object?=?new?Object();
?void?thread1()?{
?????synchronized?(object)?{?//?獲取監(jiān)視器鎖
?????????try?{
?????????????object.wait();?//?這里會(huì)解鎖,這里會(huì)解鎖,這里會(huì)解鎖
?????????????//?順便提一下,只是解了object上的監(jiān)視器鎖,如果這個(gè)線程還持有其他對(duì)象的監(jiān)視器鎖,這個(gè)時(shí)候是不會(huì)釋放的。
?????????}?catch?(InterruptedException?e)?{
?????????????//?do?somethings
?????????}
?????}
?}

2.線程 t 不會(huì)執(zhí)行任何進(jìn)一步的指令,直到它從 m 的等待集合中移出(也就是等待喚醒)。在發(fā)生以下操作的時(shí)候,線程 t 會(huì)從 m 的等待集合中移出,然后在之后的某個(gè)時(shí)間點(diǎn)恢復(fù),并繼續(xù)執(zhí)行之后的指令。

并不是說線程移出等待隊(duì)列就馬上往下執(zhí)行,這個(gè)線程還需要重新獲取鎖才行,這里也很關(guān)鍵,請(qǐng)往后看17.2.4中我寫的兩個(gè)簡(jiǎn)單的例子。

  • 在 m上執(zhí)行了 notify 操作,而且線程 t 被選中從等待集合中移除。
  • 在 m 上執(zhí)行了 notifyAll 操作,那么線程 t 會(huì)從等待集合中移除。
  • 線程 t 發(fā)生了 interrupt 操作。
  • 如果線程 t 是調(diào)用 wait(millisecs) 或者 wait(millisecs, nanosecs) 方法進(jìn)入等待集合的,那么過了millisecs 毫秒或者 (millisecs*1000000+nanosecs) 納秒后,線程 t也會(huì)從等待集合中移出。
  • JVM 的“假喚醒”,雖然這是不鼓勵(lì)的,但是這種操作是被允許的,這樣 JVM 能實(shí)現(xiàn)將線程從等待集合中移出,而不必等待具體的移出指令。

注意,良好的 Java 編碼習(xí)慣是,只在循環(huán)中使用 wait 方法,這個(gè)循環(huán)等待某些條件來退出循環(huán)。

個(gè)人理解wait方法是這么用的:

?synchronized(m)?{
?????while(!canExit)?{
???????m.wait(10);?//?等待10ms;?當(dāng)然中斷也是常用的
???????canExit?=?something();??//?判斷是否可以退出循環(huán)
?????}
?}
?// 2 個(gè)知識(shí)點(diǎn):
?//?1.?必須先獲取到對(duì)象上的監(jiān)視器鎖
?//?2.?wait?有可能被假喚醒

每個(gè)線程在一系列?可能導(dǎo)致它從等待集合中移出的事件?中必須決定一個(gè)順序。這個(gè)順序不必要和其他順序一致,但是線程必須表現(xiàn)為它是按照那個(gè)順序發(fā)生的。

例如,線程 t 現(xiàn)在在 m 的等待集合中,不管是線程 t 中斷還是 m 的 notify 方法被調(diào)用,這些操作事件肯定存在一個(gè)順序。如果線程 t 的中斷先發(fā)生,那么 t 會(huì)因?yàn)?InterruptedException 異常而從 wait 方法中返回,同時(shí) m 的等待集合中的其他線程(如果有的話)會(huì)收到這個(gè)通知。如果 m 的 notify 先發(fā)生,那么 t 會(huì)正常從 wait 方法返回,且不會(huì)改變中斷狀態(tài)。

我們考慮這個(gè)場(chǎng)景:

線程 1 和線程 2 此時(shí)都 wait 了,線程 3 調(diào)用了 :

synchronized?(object)?{
????thread1.interrupt();?//1
????object.notify();??//2
}

本來我以為上面的情況 線程1 一定是拋出 InterruptedException,線程2 是正常返回的。感謝評(píng)論留言的 xupeng.zhang,我的這個(gè)想法是錯(cuò)誤的,完全有可能線程1正常返回(即使其中斷狀態(tài)是true),線程2 一直 wait。

3.線程 t 執(zhí)行編號(hào)為 n 的加鎖操作

回去看 2 ?說了什么,線程剛剛從等待集合中移出,然后這里需要重新獲取監(jiān)視器鎖才能繼續(xù)往下執(zhí)行。

4.如果線程 t 在 2 的時(shí)候由于中斷而從 m 的等待集合中移出,那么它的中斷狀態(tài)會(huì)重置為 false,同時(shí) wait 方法會(huì)拋出 InterruptedException 異常。

這一節(jié)主要在講線程進(jìn)出等待集合的各種情況,同時(shí),最好要知道中斷是怎么用的,中斷的狀態(tài)重置發(fā)生于什么時(shí)候。

這里的 1,2,3,4 的發(fā)生順序非常關(guān)鍵,大家可以仔細(xì)再看看是不是完全理解了,之后的幾個(gè)小節(jié)還會(huì)更具體地闡述這個(gè),參考代碼請(qǐng)看 17.2.4 小節(jié)我寫的簡(jiǎn)單的例子。

17.2.2 通知(Notification)

通知操作發(fā)生于調(diào)用 notify 和 notifyAll 方法。

我們?cè)诰€程 t 中對(duì)對(duì)象 m 調(diào)用 m.notify() 或 m.notifyAll() 方法,n 代表加鎖編號(hào),同時(shí)對(duì)應(yīng)的解鎖操作沒有執(zhí)行,則下面的其中之一會(huì)發(fā)生:

  • 如果 n 等于 0,拋出 IllegalMonitorStateException 異常,因?yàn)榫€程 t 還沒有獲取到對(duì)象 m 上的鎖。

這一點(diǎn)很關(guān)鍵,只有獲取到了對(duì)象上的監(jiān)視器鎖的線程才可以正常調(diào)用 notify,前面我們也說過,調(diào)用 wait 方法的時(shí)候也要先獲取鎖

  • 如果 n 大于 0,而且這是一個(gè) notify 操作,如果 m 的等待集合不為空,那么等待集合中的線程 u 被選中從等待集合中移出。

對(duì)于哪個(gè)線程會(huì)被選中而被移出,虛擬機(jī)沒有提供任何保證,從等待集合中將線程 u 移出,可以讓線程 u 得以恢復(fù)。注意,恢復(fù)之后的線程 u 如果對(duì) m 進(jìn)行加鎖操作將不會(huì)成功,直到線程 t 完全釋放鎖之后。

因?yàn)榫€程 t 這個(gè)時(shí)候還持有 m 的鎖。這個(gè)知識(shí)點(diǎn)在 17.2.4 節(jié)我還會(huì)重點(diǎn)說。這里記住,被 notify 的線程在喚醒后是需要重新獲取監(jiān)視器鎖的。

  • 如果 n 大于 0,而且這是一個(gè) notifyAll 操作,那么等待集合中的所有線程都將從等待集合中移出,然后恢復(fù)。

注意,這些線程恢復(fù)后,只有一個(gè)線程可以鎖住監(jiān)視器。

本小節(jié)結(jié)束,通知操作相對(duì)來說還是很簡(jiǎn)單的吧。

17.2.3 中斷(Interruptions)

中斷發(fā)生于 Thread.interrupt 方法的調(diào)用。

令線程 t 調(diào)用線程 u 上的方法 u.interrupt(),其中 t 和 u 可以是同一個(gè)線程,這個(gè)操作會(huì)將 u 的中斷狀態(tài)設(shè)置為 true。

順便說說中斷狀態(tài)吧,初學(xué)者肯定以為 thread.interrupt() 方法是用來暫停線程的,主要是和它對(duì)應(yīng)中文翻譯的“中斷”有關(guān)。中斷在并發(fā)中是常用的手段,請(qǐng)大家一定好好掌握。可以將中斷理解為線程的狀態(tài),它的特殊之處在于設(shè)置了中斷狀態(tài)為 true 后,這幾個(gè)方法會(huì)感知到:

  • wait(), wait(long), wait(long, int), join(), join(long), join(long, int), sleep(long), sleep(long, int)這些方法都有一個(gè)共同之處,方法簽名上都有throws InterruptedException,這個(gè)就是用來響應(yīng)中斷狀態(tài)修改的。
  • 如果線程阻塞在 InterruptibleChannel 類的 IO 操作中,那么這個(gè) channel 會(huì)被關(guān)閉。
  • 如果線程阻塞在一個(gè) Selector 中,那么 select 方法會(huì)立即返回。

如果線程阻塞在以上3種情況中,那么當(dāng)線程感知到中斷狀態(tài)后(此線程的 interrupt() 方法被調(diào)用),會(huì)將中斷狀態(tài)重新設(shè)置為 false,然后執(zhí)行相應(yīng)的操作(通常就是跳到 catch 異常處)。

如果不是以上3種情況,那么,線程的 interrupt() 方法被調(diào)用,會(huì)將線程的中斷狀態(tài)設(shè)置為 true。

當(dāng)然,除了這幾個(gè)方法,我知道的是 LockSupport 中的 park 方法也能自動(dòng)感知到線程被中斷,當(dāng)然,它不會(huì)重置中斷狀態(tài)為 false。我們說了,只有上面的幾種情況會(huì)在感知到中斷后先重置中斷狀態(tài)為 false,然后再繼續(xù)執(zhí)行。

另外,如果有一個(gè)對(duì)象 m,而且線程 u 此時(shí)在 m 的等待集合中,那么 u 將會(huì)從 m 的等待集合中移出。這會(huì)讓 u 從 wait 操作中恢復(fù)過來,u 此時(shí)需要獲取 m 的監(jiān)視器鎖,獲取完鎖以后,發(fā)現(xiàn)線程 u 處于中斷狀態(tài),此時(shí)會(huì)拋出 InterruptedException 異常。

這里的流程:t 設(shè)置 u 的中斷狀態(tài) => u 線程恢復(fù) => u 獲取 m 的監(jiān)視器鎖 => 獲取鎖以后,拋出 InterruptedException 異常。

這個(gè)流程在前面 wait 的小節(jié)已經(jīng)講過了,這也是很多人都不了解的知識(shí)點(diǎn)。如果還不懂,可以看下一小節(jié)的結(jié)束,我的兩個(gè)簡(jiǎn)單的例子。

一個(gè)小細(xì)節(jié):u 被中斷,wait 方法返回,并不會(huì)立即拋出 InterruptedException 異常,而是在重新獲取監(jiān)視器鎖之后才會(huì)拋出異常。

實(shí)例方法 thread.isInterrupted() 可以知道線程的中斷狀態(tài)。

調(diào)用靜態(tài)方法 Thread.interrupted() 可以返回當(dāng)前線程的中斷狀態(tài),同時(shí)將中斷狀態(tài)設(shè)置為false。

所以說,如果是這個(gè)方法調(diào)用兩次,那么第二次一定會(huì)返回 false,因?yàn)榈谝淮螘?huì)重置狀態(tài)。當(dāng)然了,前提是兩次調(diào)用的中間沒有發(fā)生設(shè)置線程中斷狀態(tài)的其他語句。

17.2.4 等待、通知和中斷的交互(Interactions of Waits, Notification, and Interruption)

以上的一系列規(guī)范能讓我們確定 在等待、通知、中斷的交互中 有關(guān)的幾個(gè)屬性。

如果一個(gè)線程在等待期間,同時(shí)發(fā)生了通知和中斷,它將發(fā)生:

  • 從 wait 方法中正常返回,同時(shí)不改變中斷狀態(tài)(也就是說,調(diào)用 Thread.interrupted 方法將會(huì)返回 true)
  • 由于拋出了 InterruptedException 異常而從 wait 方法中返回,中斷狀態(tài)設(shè)置為 false

線程可能沒有重置它的中斷狀態(tài),同時(shí)從 wait 方法中正常返回,即第一種情況。

也就是說,線程是從 notify 被喚醒的,由于發(fā)生了中斷,所以中斷狀態(tài)為 true

同樣的,通知也不能由于中斷而丟失。

這個(gè)要說的是,線程其實(shí)是從中斷喚醒的,那么線程醒過來,同時(shí)中斷狀態(tài)會(huì)被重置為 false。

假設(shè) m 的等待集合為 線程集合 s,并且在另一個(gè)線程中調(diào)用了 m.notify(), 那么將發(fā)生:

  • 至少有集合 s 中的一個(gè)線程正常從 wait 方法返回,或者
  • 集合 s 中的所有線程由拋出 InterruptedException 異常而返回。

考慮是否有這個(gè)場(chǎng)景:x 被設(shè)置了中斷狀態(tài),notify 選中了集合中的線程 x,那么這次 notify 將喚醒線程 x,其他線程(我們假設(shè)還有其他線程在等待)不會(huì)有變化。

答案:存在這種場(chǎng)景。因?yàn)檫@種場(chǎng)景是滿足上述條件的,而且此時(shí) x 的中斷狀態(tài)是 true。

注意,如果一個(gè)線程同時(shí)被中斷和通知喚醒,同時(shí)這個(gè)線程通過拋出 InterruptedException 異常從 wait 中返回,那么等待集合中的某個(gè)其他線程一定會(huì)被通知。

下面我們通過 3 個(gè)例子簡(jiǎn)單分析下 wait、notify、中斷 它們的組合使用。

第一個(gè)例子展示了 wait 和 notify 操作過程中的監(jiān)視器鎖的 持有、釋放 的問題。考慮以下操作:

public?class?WaitNotify?{

????public?static?void?main(String[]?args)?{

????????Object?object?=?new?Object();

????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{

????????????????synchronized?(object)?{
????????????????????System.out.println("線程1?獲取到監(jiān)視器鎖");
????????????????????try?{
????????????????????????object.wait();
????????????????????????System.out.println("線程1 恢復(fù)啦。我為什么這么久才恢復(fù),因?yàn)閚otify方法雖然早就發(fā)生了,可是我還要獲取鎖才能繼續(xù)執(zhí)行。");
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????System.out.println("線程1?wait方法拋出了InterruptedException異常");
????????????????????}
????????????????}
????????????}
????????},?"線程1").start();

????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????synchronized?(object)?{
????????????????????System.out.println("線程2 拿到了監(jiān)視器鎖。為什么呢,因?yàn)榫€程1 在 wait 方法的時(shí)候會(huì)自動(dòng)釋放鎖");
????????????????????System.out.println("線程2?執(zhí)行?notify?操作");
????????????????????object.notify();
????????????????????System.out.println("線程2 執(zhí)行完了 notify,先休息3秒再說。");
????????????????????try?{
????????????????????????Thread.sleep(3000);
????????????????????????System.out.println("線程2 休息完啦。注意了,調(diào)sleep方法和wait方法不一樣,不會(huì)釋放監(jiān)視器鎖");
????????????????????}?catch?(InterruptedException?e)?{

????????????????????}
????????????????????System.out.println("線程2?休息夠了,結(jié)束操作");
????????????????}
????????????}
????????},?"線程2").start();
????}
}

output:
線程1?獲取到監(jiān)視器鎖
線程2 拿到了監(jiān)視器鎖。為什么呢,因?yàn)榫€程1 在?wait?方法的時(shí)候會(huì)自動(dòng)釋放鎖
線程2?執(zhí)行?notify?操作
線程2 執(zhí)行完了 notify,先休息3秒再說。
線程2 休息完啦。注意了,調(diào)sleep方法和wait方法不一樣,不會(huì)釋放監(jiān)視器鎖
線程2?休息夠了,結(jié)束操作
線程1 恢復(fù)啦。我為什么這么久才恢復(fù),因?yàn)閚otify方法雖然早就發(fā)生了,可是我還要獲取鎖才能繼續(xù)執(zhí)行。

上面的例子展示了,wait 方法返回后,需要重新獲取監(jiān)視器鎖,才可以繼續(xù)往下執(zhí)行。

同理,我們稍微修改下以上的程序,看下中斷和 wait 之間的交互:

public?class?WaitNotify?{

????public?static?void?main(String[]?args)?{

????????Object?object?=?new?Object();

????????Thread?thread1?=?new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{

????????????????synchronized?(object)?{
????????????????????System.out.println("線程1?獲取到監(jiān)視器鎖");
????????????????????try?{
????????????????????????object.wait();
????????????????????????System.out.println("線程1 恢復(fù)啦。我為什么這么久才恢復(fù),因?yàn)閚otify方法雖然早就發(fā)生了,可是我還要獲取鎖才能繼續(xù)執(zhí)行。");
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????System.out.println("線程1?wait方法拋出了InterruptedException異常,即使是異常,我也是要獲取到監(jiān)視器鎖了才會(huì)拋出");
????????????????????}
????????????????}
????????????}
????????},?"線程1");
????????thread1.start();

????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????synchronized?(object)?{
????????????????????System.out.println("線程2 拿到了監(jiān)視器鎖。為什么呢,因?yàn)榫€程1 在 wait 方法的時(shí)候會(huì)自動(dòng)釋放鎖");
????????????????????System.out.println("線程2?設(shè)置線程1?中斷");
????????????????????thread1.interrupt();
????????????????????System.out.println("線程2 執(zhí)行完了?中斷,先休息3秒再說。");
????????????????????try?{
????????????????????????Thread.sleep(3000);
????????????????????????System.out.println("線程2 休息完啦。注意了,調(diào)sleep方法和wait方法不一樣,不會(huì)釋放監(jiān)視器鎖");
????????????????????}?catch?(InterruptedException?e)?{

????????????????????}
????????????????????System.out.println("線程2?休息夠了,結(jié)束操作");
????????????????}
????????????}
????????},?"線程2").start();
????}
}
output:
線程1?獲取到監(jiān)視器鎖
線程2 拿到了監(jiān)視器鎖。為什么呢,因?yàn)榫€程1 在?wait?方法的時(shí)候會(huì)自動(dòng)釋放鎖
線程2?設(shè)置線程1?中斷
線程2 執(zhí)行完了?中斷,先休息3秒再說。
線程2 休息完啦。注意了,調(diào)sleep方法和wait方法不一樣,不會(huì)釋放監(jiān)視器鎖
線程2?休息夠了,結(jié)束操作
線程1?wait方法拋出了InterruptedException異常,即使是異常,我也是要獲取到監(jiān)視器鎖了才會(huì)拋出

上面的這個(gè)例子也很清楚,如果線程調(diào)用 wait 方法,當(dāng)此線程被中斷的時(shí)候,wait 方法會(huì)返回,然后重新獲取監(jiān)視器鎖,然后拋出InterruptedException 異常。

我們?cè)賮砜紤]下,之前說的 notify 和中斷:

package?com.javadoop.learning;

/**
?*?Created?by?hongjie?on?2017/7/7.
?*/
public?class?WaitNotify?{

????volatile?int?a?=?0;

????public?static?void?main(String[]?args)?{

????????Object?object?=?new?Object();

????????WaitNotify?waitNotify?=?new?WaitNotify();

????????Thread?thread1?=?new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{

????????????????synchronized?(object)?{
????????????????????System.out.println("線程1?獲取到監(jiān)視器鎖");
????????????????????try?{
????????????????????????object.wait();
????????????????????????System.out.println("線程1 正?;謴?fù)啦。");
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????System.out.println("線程1?wait方法拋出了InterruptedException異常");
????????????????????}
????????????????}
????????????}
????????},?"線程1");
????????thread1.start();

????????Thread?thread2?=?new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{

????????????????synchronized?(object)?{
????????????????????System.out.println("線程2?獲取到監(jiān)視器鎖");
????????????????????try?{
????????????????????????object.wait();
????????????????????????System.out.println("線程2 正?;謴?fù)啦。");
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????System.out.println("線程2?wait方法拋出了InterruptedException異常");
????????????????????}
????????????????}
????????????}
????????},?"線程2");
????????thread2.start();

?????????//?這里讓?thread1?和?thread2?先起來,然后再起后面的?thread3
????????try?{
????????????Thread.sleep(1000);
????????}?catch?(InterruptedException?e)?{
????????}

????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????synchronized?(object)?{
????????????????????System.out.println("線程3 拿到了監(jiān)視器鎖。");
????????????????????System.out.println("線程3?設(shè)置線程1中斷");
????????????????????thread1.interrupt();?//?1
????????????????????waitNotify.a?=?1;?//?這行是為了禁止上下的兩行中斷和notify代碼重排序
????????????????????System.out.println("線程3?調(diào)用notify");
????????????????????object.notify();?//2
????????????????????System.out.println("線程3?調(diào)用完notify后,休息一會(huì)");
????????????????????try?{
????????????????????????Thread.sleep(3000);
????????????????????}?catch?(InterruptedException?e)?{
????????????????????}
????????????????????System.out.println("線程3?休息夠了,結(jié)束同步代碼塊");
????????????????}
????????????}
????????},?"線程3").start();
????}
}

//?最常見的output:
線程1?獲取到監(jiān)視器鎖
線程2?獲取到監(jiān)視器鎖
線程3 拿到了監(jiān)視器鎖。
線程3?設(shè)置線程1中斷
線程3?調(diào)用notify
線程3?調(diào)用完notify后,休息一會(huì)
線程3?休息夠了,結(jié)束同步代碼塊
線程2 正?;謴?fù)啦。
線程1?wait方法拋出了InterruptedException異常

上述輸出不是絕對(duì)的,有可能發(fā)生 線程1 是正常恢復(fù)的,雖然發(fā)生了中斷,它的中斷狀態(tài)也確實(shí)是 true,但是它沒有拋出 InterruptedException,而是正常返回。此時(shí),thread2 將得不到喚醒,一直 wait。

17.3. 休眠和禮讓(Sleep and Yield)

Thread.sleep(millisecs) 使當(dāng)前正在執(zhí)行的線程休眠指定的一段時(shí)間(暫時(shí)停止執(zhí)行任何指令),時(shí)間取決于參數(shù)值,精度受制于系統(tǒng)的定時(shí)器。休眠期間,線程不會(huì)釋放任何的監(jiān)視器鎖。線程的恢復(fù)取決于定時(shí)器和處理器的可用性,即有可用的處理器來喚醒線程。

需要注意的是,Thread.sleep 和 Thread.yield 都不具有同步的語義。在 Thread.sleep 和 Thread.yield 方法調(diào)用之前,不要求虛擬機(jī)將寄存器中的緩存刷出到共享內(nèi)存中,同時(shí)也不要求虛擬機(jī)在這兩個(gè)方法調(diào)用之后,重新從共享內(nèi)存中讀取數(shù)據(jù)到緩存。

例如,我們有如下代碼塊,this.done 定義為一個(gè) non-volatile 的屬性,初始值為 false。

while?(!this.done)
????Thread.sleep(1000);

編譯器可以只讀取一次 this.done 到緩存中,然后一直使用緩存中的值,也就是說,這個(gè)循環(huán)可能永遠(yuǎn)不會(huì)結(jié)束,即使是有其他線程將 this.done 的值修改為 true。

yield 是告訴操作系統(tǒng)的調(diào)度器:我的cpu可以先讓給其他線程。注意,調(diào)度器可以不理會(huì)這個(gè)信息。

這個(gè)方法太雞肋,幾乎沒用。

17.4 內(nèi)存模型(Memory Model)

內(nèi)存模型這一節(jié)比較長(zhǎng),請(qǐng)耐心閱讀

內(nèi)存模型描述的是程序在 JVM 的執(zhí)行過程中對(duì)數(shù)據(jù)的讀寫是否是按照程序的規(guī)則正確執(zhí)行的。Java 內(nèi)存模型定義了一系列規(guī)則,這些規(guī)則定義了對(duì)共享內(nèi)存的寫操作對(duì)于讀操作的可見性。

簡(jiǎn)單地說,定義內(nèi)存模型,主要就是為了規(guī)范多線程程序中修改或者訪問同一個(gè)值的時(shí)候的行為。對(duì)于那些本身就是線程安全的問題,這里不做討論。

內(nèi)存模型描述了程序執(zhí)行時(shí)的可能的表現(xiàn)行為。只要執(zhí)行的結(jié)果是滿足 java 內(nèi)存模型的所有規(guī)則,那么虛擬機(jī)對(duì)于具體的實(shí)現(xiàn)可以自由發(fā)揮。

從側(cè)面說,不管虛擬機(jī)的實(shí)現(xiàn)是怎么樣的,多線程程序的執(zhí)行結(jié)果都應(yīng)該是可預(yù)測(cè)的。

虛擬機(jī)實(shí)現(xiàn)者可以自由地執(zhí)行大量的代碼轉(zhuǎn)換,包括重排序操作和刪除一些不必要的同步。

這里我畫了一條線,從這條線到下一條線之間是兩個(gè)重排序的例子,如果你沒接觸過,可以看一下,如果你已經(jīng)熟悉了或者在其他地方看過了,請(qǐng)直接往下滑。

示例 17.4-1 不正確的同步可能導(dǎo)致奇怪的結(jié)果

java語言允許 compilers 和 CPU 對(duì)執(zhí)行指令進(jìn)行重排序,導(dǎo)致我們會(huì)經(jīng)??吹剿剖嵌堑默F(xiàn)象。

這里沒有翻譯 compiler 為編譯器,因?yàn)樗粌H僅代表編譯器,后續(xù)它會(huì)代表所有會(huì)導(dǎo)致指令重排序的機(jī)制。

如表 17.4-A 中所示,A 和 B 是共享屬性,r1 和 r2 是局部變量。初始時(shí),令 A == B == 0。

表17.4-A. 重排序?qū)е缕婀值慕Y(jié)果 - 原始代碼

按照我們的直覺來說,r2 == 2 同時(shí) r1 == 1 應(yīng)該是不可能的。直觀地說,指令 1 和 3 應(yīng)該是最先執(zhí)行的。如果指令 1 最先執(zhí)行,那么它應(yīng)該不會(huì)看到指令 4 對(duì) A 的寫入操作。如果指令 3 最先執(zhí)行,那么它應(yīng)該不會(huì)看到執(zhí)行 2 對(duì) B 的寫入操作。

如果真的表現(xiàn)出了 r2==2 和 r1==1,那么我們應(yīng)該知道,指令 4 先于指令 1 執(zhí)行了。

如果在執(zhí)行過程出表現(xiàn)出這種行為( r2==2 和r1==1),那么我們可以推斷出以下指令依次執(zhí)行:指令 4 => 指令 1=> 指令 2 => 指令 3??瓷先?,這種順序是荒謬的。

但是,Java 是允許 compilers 對(duì)指令進(jìn)行重排序的,只要保證在單線程的情況下,能保證程序是按照我們想要的結(jié)果進(jìn)行執(zhí)行,即 compilers 可以對(duì)單線程內(nèi)不產(chǎn)生數(shù)據(jù)依賴的語句之間進(jìn)行重排序。如果指令 1 和指令 2 發(fā)生了重排序,如按照表17.4-B 所示的順序進(jìn)行執(zhí)行,那么我們就很容易看到,r2==2 和 r1==1 是可能發(fā)生的。

表 17.4-B. 重排序?qū)е缕婀值慕Y(jié)果 - 允許的編譯器轉(zhuǎn)換

B = 1; ?=> ?r1 = B; ?=> ?A = 2; ?=> ?r2 = A;

對(duì)于很多程序員來說,這個(gè)結(jié)果看上去是 broken 的,但是這段代碼是沒有正確的同步導(dǎo)致的:

  • 其中有一個(gè)線程執(zhí)行了寫操作
  • 另一個(gè)線程對(duì)同一個(gè)屬性執(zhí)行了讀操作
  • 同時(shí),讀操作和寫操作沒有使用同步來確定它們之間的執(zhí)行順序

簡(jiǎn)單地說,之后要講的一大堆東西主要就是為了確定共享內(nèi)存讀寫的執(zhí)行順序,不正確或者說非法的代碼就是因?yàn)樽x寫同一內(nèi)存地址沒有使用同步(這里不僅僅只是說synchronized),從而導(dǎo)致執(zhí)行的結(jié)果具有不確定性。

這個(gè)是?數(shù)據(jù)競(jìng)爭(zhēng)(data race)?的一個(gè)例子。當(dāng)代碼包含數(shù)據(jù)競(jìng)爭(zhēng)時(shí),經(jīng)常會(huì)發(fā)生違反我們直覺的結(jié)果。

有幾個(gè)機(jī)制會(huì)導(dǎo)致表 17.4-B 中的指令重排序。java 的 JIT 編譯器實(shí)現(xiàn)可能會(huì)重排序代碼,或者處理器也會(huì)做重排序操作。此外,java 虛擬機(jī)實(shí)現(xiàn)中的內(nèi)存層次結(jié)構(gòu)也會(huì)使代碼像重排序一樣。在本章中,我們將所有這些會(huì)導(dǎo)致代碼重排序的東西統(tǒng)稱為 compiler。

所以,后續(xù)我們不要再簡(jiǎn)單地將 compiler 翻譯為編譯器,不要狹隘地理解為 Java 編譯器。而是代表了所有可能會(huì)制造重排序的機(jī)制,包括 JVM 優(yōu)化、CPU 優(yōu)化等。

另一個(gè)可能產(chǎn)生奇怪的結(jié)果的示例如表 17.4-C,初始時(shí) p == q 同時(shí) p.x == 0。這個(gè)代碼也是沒有正確使用同步的;在這些寫入共享內(nèi)存的寫操作中,沒有進(jìn)行強(qiáng)制的先后排序。

Table 17.4-C

一個(gè)簡(jiǎn)單的編譯器優(yōu)化操作是會(huì)復(fù)用 r2 的結(jié)果給 r5,因?yàn)樗鼈兌际亲x取 r1.x,而且在單線程語義中,r2 到 r5之間沒有其他的相關(guān)的寫入操作,這種情況如表 17.4-D 所示。

Table 17.4-D

現(xiàn)在,我們來考慮一種情況,在線程1第一次讀取 r1.x 和 r3.x 之間,線程 2 執(zhí)行 r6=p; r6.x=3; 編譯器進(jìn)行了 r5復(fù)用 r2 結(jié)果的優(yōu)化操作,那么 r2==r5==0,r4 == 3,從程序員的角度來看,p.x 的值由 0 變?yōu)?3,然后又變?yōu)?0。

我簡(jiǎn)單整理了一下:

例子結(jié)束,回到正題

Java 內(nèi)存模型定義了在程序的每一步,哪些值是內(nèi)存可見的。對(duì)于隔離的每個(gè)線程來說,其操作是由我們線程中的語義來決定的,但是線程中讀取到的值是由內(nèi)存模型來控制的。

當(dāng)我們提到這點(diǎn)時(shí),我們說程序遵守線程內(nèi)語義,線程內(nèi)語義說的是單線程內(nèi)的語義,它允許我們基于線程內(nèi)讀操作看到的值完全預(yù)測(cè)線程的行為。如果我們要確定線程 t 中的操作是否是合法的,我們只要評(píng)估當(dāng)線程 t 在單線程環(huán)境中運(yùn)行時(shí)是否是合法的就可以,該規(guī)范的其余部分也在定義這個(gè)問題。

這段話不太好理解,首先記住“線程內(nèi)語義”這個(gè)概念,之后還會(huì)用到。我對(duì)這段話的理解是,在單線程中,我們是可以通過一行一行看代碼來預(yù)測(cè)執(zhí)行結(jié)果的,只不過,代碼中使用到的讀取內(nèi)存的值我們是不能確定的,這取決于在內(nèi)存模型這個(gè)大框架下,我們的程序會(huì)讀到的值。也許是最新的值,也許是過時(shí)的值。

此節(jié)描述除了 final 關(guān)鍵字外的java內(nèi)存模型的規(guī)范,final將在之后的17.5節(jié)介紹。

這里描述的內(nèi)存模型并不是基于 ?Java 編程語言的面向?qū)ο蟆榱撕?jiǎn)潔起見,我們經(jīng)常展示沒有類或方法定義的代碼片段。大多數(shù)示例包含兩個(gè)或多個(gè)線程,其中包含局部變量,共享全局變量或?qū)ο蟮膶?shí)例字段的語句。我們通常使用諸如 r1 或 r2 之類的變量名來表示方法或線程本地的變量。其他線程無法訪問此類變量。

17.4.1. 共享變量(Shared Variables)

所有線程都可以訪問到的內(nèi)存稱為共享內(nèi)存堆內(nèi)存。

所有的實(shí)例屬性,靜態(tài)屬性,還有數(shù)組的元素都存儲(chǔ)在堆內(nèi)存中。在本章中,我們用術(shù)語變量來表示這些元素。

局部變量、方法參數(shù)、異常對(duì)象,它們不會(huì)在線程間共享,也不會(huì)受到內(nèi)存模型定義的任何影響。

兩個(gè)線程對(duì)同一個(gè)變量同時(shí)進(jìn)行讀-寫操作寫-寫操作,我們稱之為“沖突”。

好,這一節(jié)都是廢話,愉快地進(jìn)入到下一節(jié)

17.4.2. 操作(Actions)

這一節(jié)主要是講解理論,主要就是嚴(yán)謹(jǐn)?shù)囟x操作。

線程間操作是指由一個(gè)線程執(zhí)行的動(dòng)作,可以被另一個(gè)線程檢測(cè)到或直接影響到。以下是幾種可能發(fā)生的線程間操作:

  • 讀 (普通變量,非 volatile)。讀一個(gè)變量。
  • 寫 (普通變量,非 volatile)。寫一個(gè)變量。
  • 同步操作,如下:
    • volatile 讀。讀一個(gè) volatile 變量
    • volatile 寫。寫入一個(gè) volatile 變量
    • 加鎖。對(duì)一個(gè)對(duì)象的監(jiān)視器加鎖。
    • 解鎖。解除對(duì)某個(gè)對(duì)象的監(jiān)視器鎖。
    • 線程的第一個(gè)和最后一個(gè)操作。
    • 開啟線程操作,或檢測(cè)一個(gè)線程是否已經(jīng)結(jié)束。
  • 外部操作。一個(gè)外部操作指的是可能被觀察到的在外部執(zhí)行的操作,同時(shí)它的執(zhí)行結(jié)果受外部環(huán)境控制。

簡(jiǎn)單說,外部操作的外部指的是在 JVM 之外,如 native 操作。

  • 線程分歧操作(§17.4.9)。此操作只由處于無限循環(huán)的線程執(zhí)行,在該循環(huán)中不執(zhí)行任何內(nèi)存操作、同步操作、或外部操作。如果一個(gè)線程執(zhí)行了分歧操作,那么其后將跟著無數(shù)的線程分歧操作。

分歧操作的引入是為了用來說明,線程可能會(huì)導(dǎo)致其他所有線程停頓而不能繼續(xù)執(zhí)行。

此規(guī)范僅關(guān)心線程間操作,我們不關(guān)心線程內(nèi)部的操作(比如將兩個(gè)局部變量的值相加存到第三個(gè)局部變量中)。如前文所說,所有的線程都需要遵守線程內(nèi)語義。對(duì)于線程間操作,我們經(jīng)常會(huì)簡(jiǎn)單地稱為操作。

我們用元祖< t, k, v, u >來描述一個(gè)操作:

  • t - 執(zhí)行操作的線程
  • k - 操作的類型。
  • v - 操作涉及的變量或監(jiān)視器
    • 對(duì)于加鎖操作,v 是被鎖住的監(jiān)視器;對(duì)于解鎖操作,v 是被解鎖的監(jiān)視器。
    • 如果是一個(gè)讀操作( volatile 讀或非 volatile 讀),v 是讀操作對(duì)應(yīng)的變量
    • 如果是一個(gè)寫操作( volatile 寫或非 volatile 寫),v 是寫操作對(duì)應(yīng)的變量
  • u - 唯一的標(biāo)識(shí)符標(biāo)識(shí)此操作

外部動(dòng)作元組還包含一個(gè)附加組件,其中包含由執(zhí)行操作的線程感知的外部操作的結(jié)果。這可能是關(guān)于操作的成敗的信息,以及操作中所讀的任何值。

外部操作的參數(shù)(如哪些字節(jié)寫入哪個(gè) socket)不是外部操作元祖的一部分。這些參數(shù)是通過線程中的其他操作進(jìn)行設(shè)置的,并可以通過檢查線程內(nèi)語義進(jìn)行確定。它們?cè)趦?nèi)存模型中沒有被明確討論。

在非終結(jié)執(zhí)行中,不是所有的外部操作都是可觀察的。17.4.9小節(jié)討論非終結(jié)執(zhí)行和可觀察操作。

大家看完這節(jié)最懵逼的應(yīng)該是外部操作和線程分歧操作,我簡(jiǎn)單解釋下。

外部操作大家可以理解為 Java 調(diào)用了一個(gè) native 的方法,Java 可以得到這個(gè) native 方法的返回值,但是對(duì)于具體的執(zhí)行其實(shí)不感知的,意味著 Java 其實(shí)不能對(duì)這種語句進(jìn)行重排序,因?yàn)?Java 無法知道方法體會(huì)執(zhí)行哪些指令。

引用 stackoverflow 中的一個(gè)例子:

//?method()方法中jni()是外部操作,不會(huì)和?"foo?=?42;"?這條語句進(jìn)行重排序。
class?Externalization?{?
??int?foo?=?0;?
??void?method()?{?
????jni();?//?外部操作
????foo?=?42;?
??}?
??native?void?jni();?/*?{?
??? assert foo ==?0;?//我們假設(shè)外部操作執(zhí)行的是這個(gè)。
??}?*/?
}

在上面這個(gè)例子中,顯然,jni() 與 foo = 42 之間不能進(jìn)行重排序。

再來個(gè)線程分歧操作的例子:

//?線程分歧操作阻止了重排序,所以?"foo?=?42;"?這條語句不會(huì)先執(zhí)行
class?ThreadDivergence?{?
??int?foo?=?0;?
??void?thread1()?{?
????while?(true){}?//?線程分歧操作
????foo?=?42;?
??}?

??void?thread2()?{?
????assert?foo?==?0;?//?這里永遠(yuǎn)不會(huì)失敗
??}?
}

17.4.3. 程序和程序順序(Programs and Program Order)

在每個(gè)線程 t 執(zhí)行的所有線程間動(dòng)作中,t 的程序順序是反映?根據(jù) t 的線程內(nèi)語義執(zhí)行這些動(dòng)作的順序?的總順序。

如果所有操作的執(zhí)行順序 和 代碼中的順序一致,那么一組操作就是連續(xù)一致的,并且,對(duì)變量 v 的每個(gè)讀操作 r 會(huì)看到寫操作 w 寫入的值,也就是:

  • 寫操作 w 先于 讀操作 r 完成,并且
  • 沒有其他的寫操作 w' 使得 w' 在 w 之后 r 之前發(fā)生。

連續(xù)一致性對(duì)于可見性和程序執(zhí)行順序是一個(gè)非常強(qiáng)的保證。在這種場(chǎng)景下,所有的單個(gè)操作(比如讀和寫)構(gòu)成一個(gè)統(tǒng)一的執(zhí)行順序,這個(gè)執(zhí)行順序和代碼出現(xiàn)的順序是一致的,同時(shí)每個(gè)單個(gè)操作都是原子的,且對(duì)所有線程來說立即可見。

如果程序沒有任何的數(shù)據(jù)競(jìng)爭(zhēng),那么程序的所有執(zhí)行操作將表現(xiàn)為連續(xù)一致。連續(xù)一致性 和/或 數(shù)據(jù)競(jìng)爭(zhēng)的自由仍然允許錯(cuò)誤從一組操作中產(chǎn)生。

完全不知道這句話是什么意思

如果我們用連續(xù)一致性作為我們的內(nèi)存模型,那我們討論的許多關(guān)于編譯器優(yōu)化和處理器優(yōu)化就是非法的。比如在17.4-C中,一旦執(zhí)行 p.x=3,那么后續(xù)對(duì)于該位置的讀操作應(yīng)該是立即可以讀到最新值的。

連續(xù)一致性的核心在于每一步的操作都是原子的,同時(shí)對(duì)于所有線程都是可見的,而且不存在重排序。所以,Java 語言定義的內(nèi)存模型肯定不會(huì)采用這種策略,因?yàn)樗苯酉拗屏司幾g器和 JVM 的各種優(yōu)化措施。

注意:很多地方所說的順序一致性就是這里的連續(xù)一致性,英文是 Sequential consistency

17.4.4. 同步順序(Synchronization Order)

每個(gè)執(zhí)行都有一個(gè)同步順序。同步順序是由執(zhí)行過程中的每個(gè)同步操作組成的順序。對(duì)于每個(gè)線程 t,同步操作組成的同步順序是和線程 t 中的代碼順序一致的。

雖然拗口,但畢竟說的是同步,我們都不陌生。同步操作包括了如下同步關(guān)系:

  • 對(duì)于監(jiān)視器 m 的解鎖與所有后續(xù)操作對(duì)于 m 的加鎖同步
  • 對(duì) volatile 變量 v 的寫入,與所有其他線程后續(xù)對(duì) v 的讀同步
  • 啟動(dòng)線程的操作與線程中的第一個(gè)操作同步。
  • 對(duì)于每個(gè)屬性寫入默認(rèn)值(0, false,null)與每個(gè)線程對(duì)其進(jìn)行的操作同步。
  • 盡管在創(chuàng)建對(duì)象完成之前對(duì)對(duì)象屬性寫入默認(rèn)值有點(diǎn)奇怪,但從概念上來說,每個(gè)對(duì)象都是在程序啟動(dòng)時(shí)用默認(rèn)值初始化來創(chuàng)建的。
  • 線程 T1 的最后操作與線程 T2 發(fā)現(xiàn)線程 T1 已經(jīng)結(jié)束同步。
  • 線程 T2 可以通過 T1.isAlive() 或 T1.join() 方法來判斷 T1 是否已經(jīng)終結(jié)。
  • 如果線程 T1 中斷了 T2,那么線程 T1 的中斷操作與其他所有線程發(fā)現(xiàn) T2 被中斷了同步(通過拋出 InterruptedException 異常,或者調(diào)用 Thread.interrupted 或 Thread.isInterrupted )

以上同步順序可以理解為對(duì)于某資源的釋放先于其他操作對(duì)同一資源的獲取。

好,這節(jié)相對(duì) easy,說的就是關(guān)于 A synchronizes-with B 的一系列規(guī)則。

17.4.5. Happens-before順序(Happens-before Order)

Happens-before 是非常重要的知識(shí),有些地方我沒有很理解,我盡量將原文直譯過來。想要了解更深的東西,你可能還需要查詢更多的其他資料。

兩個(gè)操作可以用 happens-before 來確定它們的執(zhí)行順序,如果一個(gè)操作 happens-before 于另一個(gè)操作,那么我們說第一個(gè)操作對(duì)于第二個(gè)操作是可見的。

注意:happens-before 強(qiáng)調(diào)的是可見性問題

如果我們分別有操作 x 和操作 y,我們寫成 hb(x, y) 來表示 x happens-before y。

  • 如果操作 x 和操作 y 是同一個(gè)線程的兩個(gè)操作,并且在代碼上操作 x 先于操作 y 出現(xiàn),那么有 hb(x, y)。請(qǐng)注意,這里不代表不可以重排序,只要沒有數(shù)據(jù)依賴關(guān)系,重排序就是可能的。
  • 對(duì)象構(gòu)造方法的最后一行指令 happens-before 于 finalize() 方法的第一行指令。
  • 如果操作 x 與隨后的操作 y 構(gòu)成同步,那么 hb(x, y)。
  • hb(x, y) 和 hb(y, z),那么可以推斷出 hb(x, z)

對(duì)象的 wait 方法關(guān)聯(lián)了加鎖和解鎖的操作,它們的 happens-before 關(guān)系即是加鎖 happens-before 解鎖。

我們應(yīng)該注意到,兩個(gè)操作之間的 happens-before 的關(guān)系并不一定表示它們?cè)?JVM 的具體實(shí)現(xiàn)上必須是這個(gè)順序,如果重排序后的操作結(jié)果和合法的執(zhí)行結(jié)果是一致的,那么這種實(shí)現(xiàn)就不是非法的。

比如說,在線程中對(duì)對(duì)象的每個(gè)屬性寫入初始默認(rèn)值并不需要先于線程的開始,只要這個(gè)事實(shí)沒有被讀到就可以了。

我們可以發(fā)現(xiàn),happens-before 規(guī)則主要還是上一節(jié) 同步順序 中的規(guī)則,加上額外的幾條

更具體地說,如果兩個(gè)操作是 happens-before 的關(guān)系,但是在代碼中它們并沒有這種順序,那么就沒有必要表現(xiàn)出 happens-before 關(guān)系。如線程 1 對(duì)變量進(jìn)行寫入,線程 2 隨后對(duì)變量進(jìn)行讀操作,那么這兩個(gè)操作是沒有 happens-before 關(guān)系的。

happens-before 關(guān)系用于定義當(dāng)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng)的時(shí)候。將上面所有的規(guī)則簡(jiǎn)化成以下列表:

  • 對(duì)一個(gè)監(jiān)視器的解鎖操作 happens-before 于后續(xù)的對(duì)這個(gè)監(jiān)視器的加鎖操作。
  • 對(duì) volatile 屬性的寫操作先于后續(xù)對(duì)這個(gè)屬性的讀操作。也就是一旦寫操作完成,那么后續(xù)的讀操作一定能讀到最新的值
  • 線程的 start() 先于任何在線程中定義的語句。
  • 如果 A 線程中調(diào)用了 B.join(),那么 B 線程中的操作先于 A 線程 join() 返回之后的任何語句。因?yàn)?join() 本身就是讓其他線程先執(zhí)行完的意思。
  • 對(duì)象的默認(rèn)初始值 happens-before 于程序中對(duì)它的其他操作。也就是說不管我們要對(duì)這個(gè)對(duì)象干什么,這個(gè)對(duì)象即使沒有創(chuàng)建完成,它的各個(gè)屬性也一定有初始零值。

當(dāng)程序出現(xiàn)兩個(gè)沒有 happens-before 關(guān)系的操作對(duì)同一數(shù)據(jù)進(jìn)行訪問時(shí),我們稱之為程序中有數(shù)據(jù)競(jìng)爭(zhēng)。

除了線程間操作,數(shù)據(jù)競(jìng)爭(zhēng)不直接影響其他操作的語義,如讀取數(shù)組的長(zhǎng)度、檢查轉(zhuǎn)換的執(zhí)行、虛擬方法的調(diào)用。

因此,數(shù)據(jù)競(jìng)爭(zhēng)不會(huì)導(dǎo)致錯(cuò)誤的行為,例如為數(shù)組返回錯(cuò)誤的長(zhǎng)度。當(dāng)且僅當(dāng)所有連續(xù)一致的操作都沒有數(shù)據(jù)爭(zhēng)用時(shí),程序就是正確同步的。

如果一個(gè)程序是正確同步的,那么程序中的所有操作就會(huì)表現(xiàn)出連續(xù)一致性。

這是一個(gè)對(duì)于程序員來說強(qiáng)有力的保證,程序員不需要知道重排序的原因,就可以確定他們的代碼是否包含數(shù)據(jù)爭(zhēng)用。因此,他們不需要知道重排序的原因,來確定他們的代碼是否是正確同步的。一旦確定了代碼是正確同步的,程序員也就不需要擔(dān)心重排序?qū)τ诖a的影響。

其實(shí)就是正確同步的代碼不存在數(shù)據(jù)競(jìng)爭(zhēng)問題,這個(gè)時(shí)候程序員不需要關(guān)心重排序是否會(huì)影響我們的代碼,我們的代碼執(zhí)行一定會(huì)表現(xiàn)出連續(xù)一致。

程序必須正確同步,以避免當(dāng)出現(xiàn)重排序時(shí),會(huì)出現(xiàn)一系列的奇怪的行為。正確同步的使用,不能保證程序的全部行為都是正確的。

但是,它的使用可以讓程序員以很簡(jiǎn)單的方式就能知道可能發(fā)生的行為。正確同步的程序表現(xiàn)出來的行為更不會(huì)依賴于可能的重排序。沒有使用正確同步,非常奇怪、令人疑惑、違反直覺的任何行為都是可能的。

我們說,對(duì)變量 v 的讀操作 r 能看到對(duì) v 的寫操作 w,如果:

讀操作 r 不是先于 w 發(fā)生(比如不是 hb(r, w) ),同時(shí)沒有寫操作 w' 穿插在 w 和 r 中間(如不存在 hb(w, w') 和 hb(w', r))。非正式地,如果沒有 happens-before 關(guān)系阻止讀操作 r,那么讀操作 r 就能看到寫操作 w 的結(jié)果。

17.5. final 屬性的語義(final Field Semantics)

我們經(jīng)常使用 final,關(guān)于它最基礎(chǔ)的知識(shí)是:用 final 修飾的類不可以被繼承,用 final 修飾的方法不可以被覆寫,用 final 修飾的屬性一旦初始化以后不可以被修改。

當(dāng)然,這節(jié)說的不是這些,這里將闡述 final 關(guān)鍵字的深層次含義。

用 final 聲明的屬性正常情況下初始化一次后,就不會(huì)被改變。final 屬性的語義與普通屬性的語義有一些不一樣。尤其是,對(duì)于 final 屬性的讀操作,compilers 可以自由地去除不必要的同步。相應(yīng)地,compilers 可以將 final 屬性的值緩存在寄存器中,而不用像普通屬性一樣從內(nèi)存中重新讀取。

final 屬性同時(shí)也允許程序員不需要使用同步就可以實(shí)現(xiàn)線程安全的不可變對(duì)象。一個(gè)線程安全的不可變對(duì)象對(duì)于所有線程來說都是不可變的,即使傳遞這個(gè)對(duì)象的引用存在數(shù)據(jù)競(jìng)爭(zhēng)。

這可以提供安全的保證,即使是錯(cuò)誤的或者惡意的對(duì)于這個(gè)不可變對(duì)象的使用。如果需要保證對(duì)象不可變,需要正確地使用 final 屬性域。

對(duì)象只有在構(gòu)造方法結(jié)束了才被認(rèn)為完全初始化了。如果一個(gè)對(duì)象完全初始化以后,一個(gè)線程持有該對(duì)象的引用,那么這個(gè)線程一定可以看到正確初始化的 final 屬性的值。

這個(gè)隱含了,如果屬性值不是 final 的,那就不能保證一定可以看到正確初始化的值,可能看到初始零值。

final 屬性的使用是非常簡(jiǎn)單的:在對(duì)象的構(gòu)造方法中設(shè)置 final 屬性;同時(shí)在對(duì)象初始化完成前,不要將此對(duì)象的引用寫入到其他線程可以訪問到的地方。如果這個(gè)條件滿足,當(dāng)其他線程看到這個(gè)對(duì)象的時(shí)候,那個(gè)線程始終可以看到正確初始化后的對(duì)象的 final 屬性。

這里面說到了一個(gè)正確初始化的問題,看過《Java并發(fā)編程實(shí)戰(zhàn)》的可能對(duì)這個(gè)會(huì)有印象,不要在構(gòu)造方法中將 this 發(fā)布出去。

這段代碼把final屬性和普通屬性進(jìn)行對(duì)比。

class?FinalFieldExample?{?
????final?int?x;
????int?y;?
????static?FinalFieldExample?f;

????public?FinalFieldExample()?{
????????x?=?3;?
????????y?=?4;?
????}?

????static?void?writer()?{
????????f?=?new?FinalFieldExample();
????}?

????static?void?reader()?{
????????if?(f?!=?null)?{
????????????int?i?=?f.x;??//?程序一定能得到?3??
????????????int?j?=?f.y;??//?也許會(huì)看到?0
????????}?
????}?
}

這個(gè)類FinalFieldExample有一個(gè) final 屬性 x 和一個(gè)普通屬性 y。我們假定有一個(gè)線程執(zhí)行 writer() 方法,另一個(gè)線程再執(zhí)行 reader() 方法。

因?yàn)?writer() 方法在對(duì)象完全構(gòu)造后將引用寫入 f,那么 reader() 方法將一定可以看到初始化后的 f.x : 將讀到一個(gè) int 值 3。然而, f.y 不是 final 的,所以程序不能保證可以看到 4,可能會(huì)得到 0。

final 屬性被設(shè)計(jì)成用來保障很多操作的安全性??紤]以下代碼,線程 1 執(zhí)行:

Global.s?=?"/tmp/usr".substring(4);

同時(shí),線程 2 執(zhí)行:

String?myS?=?Global.s;?
if?(myS.equals("/tmp"))?System.out.println(myS);

String 對(duì)象是不可變對(duì)象,同時(shí) String 操作不需要使用同步。雖然 String 的實(shí)現(xiàn)沒有任何的數(shù)據(jù)競(jìng)爭(zhēng),但是其他使用到 String 對(duì)象的代碼可能是存在數(shù)據(jù)競(jìng)爭(zhēng)的,內(nèi)存模型沒有對(duì)存在數(shù)據(jù)競(jìng)爭(zhēng)的代碼提供安全性保證。

特別是,如果 String 類中的屬性不是 final 的,那么有可能(雖然不太可能)線程 2 會(huì)看到這個(gè) string 對(duì)象的 offset 為初始值 0,那么就會(huì)出現(xiàn) myS.equals("/tmp")。

之后的一個(gè)操作可能會(huì)看到這個(gè) String 對(duì)象的正確的 offset 值 4,那么會(huì)得到 “/usr”。Java 中的許多安全特性都依賴于 String 對(duì)象的不可變性,即使是惡意代碼在數(shù)據(jù)競(jìng)爭(zhēng)的環(huán)境中在線程之間傳遞 String 對(duì)象的引用。

大家看這段的時(shí)候,如果要看代碼,請(qǐng)注意,這里說的是 ?JDK6 及以前的 String 類:

public?final?class?String??
????implements?java.io.Serializable,?Comparable,?CharSequence??
{??
????/**?The?value?is?used?for?character?storage.?*/??
????private?final?char?value[];??

????/**?The?offset?is?the?first?index?of?the?storage?that?is?used.?*/??
????private?final?int?offset;??

????/**?The?count?is?the?number?of?characters?in?the?String.?*/??
????private?final?int?count;??

????/**?Cache?the?hash?code?for?the?string?*/??
????private?int?hash;?//?Default?to?0??

因?yàn)榈?JDK7 和 JDK8 的時(shí)候,代碼已經(jīng)變?yōu)椋?/p>

public?final?class?String
????implements?java.io.Serializable,?Comparable,?CharSequence?{
????/**?The?value?is?used?for?character?storage.?*/
????private?final?char?value[];

????/**?Cache?the?hash?code?for?the?string?*/
????private?int?hash;?//?Default?to?0

????/**?use?serialVersionUID?from?JDK?1.0.2?for?interoperability?*/
????private?static?final?long?serialVersionUID?=?-6849794470754667710L;

17.5.1. final屬性的語義(Semantics of final Fields)

令 o 為一個(gè)對(duì)象,c 為 o 的構(gòu)造方法,構(gòu)造方法中對(duì) final 的屬性 f 進(jìn)行寫入值。當(dāng)構(gòu)造方法 c 退出的時(shí)候,會(huì)在final 屬性 f 上執(zhí)行一個(gè) freeze 操作。

注意,如果一個(gè)構(gòu)造方法調(diào)用了另一個(gè)構(gòu)造方法,在被調(diào)用的構(gòu)造方法中設(shè)置 final 屬性,那么對(duì)于 final 屬性的 freeze 操作發(fā)生于被調(diào)用的構(gòu)造方法結(jié)束的時(shí)候。

對(duì)于每一個(gè)執(zhí)行,讀操作的行為被其他的兩個(gè)偏序影響,解引用鏈 dereferences() 和內(nèi)存鏈 mc(),它們被認(rèn)為是執(zhí)行的一部分。這些偏序必須滿足下面的約束:

17.5.2. 在構(gòu)造期間讀 final 屬性(Reading final Fields During Construction)

在構(gòu)造對(duì)象的線程中,對(duì)該對(duì)象的 final 屬性的讀操作,遵守正常的 happens-before 規(guī)則。如果在構(gòu)造方法內(nèi),讀某個(gè) final 屬性晚于對(duì)這個(gè)屬性的寫操作,那么這個(gè)讀操作可以看到這個(gè) final 屬性已經(jīng)被定義的值,否則就會(huì)看到默認(rèn)值。

17.5.3. final 屬性的修改(Subsequent Modification of final Fields)

在許多場(chǎng)景下,如反序列化,系統(tǒng)需要在對(duì)象構(gòu)造之后改變 final 屬性的值。final 屬性可以通過反射和其他方法來改變。

唯一的具有合理語義的模式是:對(duì)象被構(gòu)造出來,然后對(duì)象中的 final 屬性被更新。在這個(gè)對(duì)象的所有 final 屬性更新操作完成之前,此對(duì)象不應(yīng)該對(duì)其他線程可見,也不應(yīng)該對(duì) final 屬性進(jìn)行讀操作。

對(duì)于 final 屬性的 freeze 操作發(fā)生于構(gòu)造方法的結(jié)束,這個(gè)時(shí)候 final 屬性已經(jīng)被設(shè)值,還有通過反射或其他方式對(duì)于 final 屬性的更新之后。

即使是這樣,依然存在幾個(gè)難點(diǎn)。如果一個(gè) final 屬性在屬性聲明的時(shí)候初始化為一個(gè)常量表達(dá)式,對(duì)于這個(gè) final 屬性值的變化過程也許是不可見的,因?yàn)閷?duì)于這個(gè) final 屬性的使用是在編譯時(shí)用常量表達(dá)式來替換的。

另一個(gè)問題是,該規(guī)范允許 JVM 實(shí)現(xiàn)對(duì) final 屬性進(jìn)行強(qiáng)制優(yōu)化。在一個(gè)線程內(nèi),允許對(duì)于 final 屬性的讀操作與構(gòu)造方法之外的對(duì)于這個(gè) final 屬性的修改進(jìn)行重排序。

對(duì)于 final 屬性的強(qiáng)制優(yōu)化(Aggressive Optimization of final Fields)

class?A?{
????final?int?x;
????A()?{?
????????x?=?1;?
????}?

????int?f()?{?
????????return?d(this,this);?
????}?

????int?d(A?a1,?A?a2)?{?
????????int?i?=?a1.x;?
????????g(a1);?
????????int?j?=?a2.x;?
????????return?j?-?i;?
????}

????static?void?g(A?a)?{?
????????//?利用反射將?a.x?的值修改為?2
????????//?uses?reflection?to?change?a.x?to?2?
????}?
}

在方法 d 中,編譯器允許對(duì) x 的讀操作和方法 g 進(jìn)行重排序,這樣的話,new A().f()可能會(huì)返回 -1, 0, 或 1。

我在我的 MBP 上試了好多辦法,真的沒法重現(xiàn)出來,不過并發(fā)問題就是這樣,我們不能重現(xiàn)不代表不存在。StackOverflow 上有網(wǎng)友說在 Sparc 上運(yùn)行,可惜我沒有 Sparc 機(jī)器。

下文將說到一個(gè)比較少見的 final-field-safe context

JVM 實(shí)現(xiàn)可以提供一種方式在 final 屬性安全上下文(final-field-safe context)中執(zhí)行代碼塊。如果一個(gè)對(duì)象是在 final 屬性安全上下文中構(gòu)造出來的,那么在這個(gè) final 屬性安全上下文 中對(duì)于 final 屬性的讀操作不會(huì)和相應(yīng)的對(duì)于 final 屬性的修改進(jìn)行重排序。

final 屬性安全上下文還提供了額外的保障。如果一個(gè)線程已經(jīng)看到一個(gè)不正確發(fā)布的一個(gè)對(duì)象的引用,那么此線程可以看到了 final 屬性的默認(rèn)值,然后,在 final 屬性安全上下文中讀取該對(duì)象的正確發(fā)布的引用,這可以保證看到正確的 final 屬性的值。在形式上,在final 屬性安全上下文中執(zhí)行的代碼被認(rèn)為是一個(gè)獨(dú)立的線程(僅用于滿足 final 屬性的語義)。

在實(shí)現(xiàn)中,compiler 不應(yīng)該將對(duì) final 屬性的訪問移入或移出final 屬性安全上下文(盡管它可以在這個(gè)執(zhí)行上下文的周邊移動(dòng),只要這個(gè)對(duì)象沒有在這個(gè)上下文中進(jìn)行構(gòu)造)。

對(duì)于 final 屬性安全上下文的使用,一個(gè)恰當(dāng)?shù)牡胤绞菆?zhí)行器或者線程池。在每個(gè)獨(dú)立的 final 屬性安全上下文中執(zhí)行每一個(gè) Runnable,執(zhí)行器可以保證在一個(gè) Runnable 中對(duì)對(duì)象 o 的不正確的訪問不會(huì)影響同一執(zhí)行器內(nèi)的其他 Runnable 中的 final 帶來的安全保障。

17.5.4. 寫保護(hù)屬性(Write-Protected Fields)

通常,如果一個(gè)屬性是 final 的和 static 的,那么這個(gè)屬性是不會(huì)被改變的。但是, System.in, System.out, 和 System.err 是 static final 的,出于遺留的歷史原因,它們必須允許被 System.setIn, System.setOut, 和 System.setErr 這幾個(gè)方法改變。我們稱這些屬性是寫保護(hù)的,用以區(qū)分普通的 final 屬性。

??public?final?static?InputStream?in?=?null;
????public?final?static?PrintStream?out?=?null;
????public?final?static?PrintStream?err?=?null;

編譯器需要將這些屬性與 final 屬性區(qū)別對(duì)待。例如,普通 final 屬性的讀操作對(duì)于同步是“免疫的”:鎖或 volatile 讀操作中的內(nèi)存屏障并不會(huì)影響到對(duì)于 final 屬性的讀操作讀到的值。因?yàn)閷懕Wo(hù)屬性的值是可以被改變的,所以同步事件應(yīng)該對(duì)它們有影響。因此,語義規(guī)定這些屬性被當(dāng)做普通屬性,不能被用戶的代碼改變,除非是 System類中的代碼。

17.6. 字分裂(Word Tearing)

實(shí)現(xiàn) Java 虛擬機(jī)需要考慮的一件事情是,每個(gè)對(duì)象屬性以及數(shù)組元素之間是獨(dú)立的,更新一個(gè)屬性或元素不能影響其他屬性或元素的讀取與更新。尤其是,兩個(gè)線程在分別更新 byte 數(shù)組相鄰的元素時(shí),不能互相影響與干擾,且不需要同步來保證連續(xù)一致性。

一些處理器不提供寫入單個(gè)字節(jié)的能力。通過簡(jiǎn)單地讀取整個(gè)字,更新相應(yīng)的字節(jié),然后將整個(gè)字寫入內(nèi)存,用這種方式在這種處理器上實(shí)現(xiàn)字節(jié)數(shù)組更新是非法的。這個(gè)問題有時(shí)被稱為字分裂(word tearing),在這種不能單獨(dú)更新單個(gè)字節(jié)的處理器上,將需要尋求其他的方法。

請(qǐng)注意,對(duì)于大部分處理器來說,都沒有這個(gè)問題

Example 17.6-1. Detection of Word Tearing

以下程序用于測(cè)試是否存在字分裂:

public?class?WordTearing?extends?Thread?{
????static?final?int?LENGTH?=?8;
????static?final?int?ITERS?=?1000000;
????static?byte[]?counts?=?new?byte[LENGTH];
????static?Thread[]?threads?=?new?Thread[LENGTH];

????final?int?id;

????WordTearing(int?i)?{
????????id?=?i;
????}

????public?void?run()?{
????????byte?v?=?0;
????????for?(int?i?=?0;?i?????????????byte?v2?=?counts[id];
????????????if?(v?!=?v2)?{
????????????????System.err.println("Word-Tearing?found:?"?+
????????????????????????"counts["?+?id?+?"]?=?"?+?v2?+
????????????????????????",?should?be?"?+?v);
????????????????return;
????????????}
????????????v++;
????????????counts[id]?=?v;
????????}
????????System.out.println("done");
????}

????public?static?void?main(String[]?args)?{
????????for?(int?i?=?0;?i?????????????(threads[i]?=?new?WordTearing(i)).start();
????}
}

這表明寫入字節(jié)時(shí)不得覆寫相鄰的字節(jié)。

17.7. double 和 long 的非原子處理 (Non-Atomic Treatment of double and long)

在Java內(nèi)存模型中,對(duì)于 non-volatile 的 long 或 double 值的寫入是通過兩個(gè)單獨(dú)的寫操作完成的:long 和 double 是 64 位的,被分為兩個(gè) 32 位來進(jìn)行寫入。那么可能就會(huì)導(dǎo)致一個(gè)線程看到了某個(gè)操作的低 32 位的寫入和另一個(gè)操作的高 32 位的寫入。

寫入或者讀取 volatile 的 long 和 double 值是原子的。

寫入和讀取對(duì)象引用一定是原子的,不管具體實(shí)現(xiàn)是32位還是64位。

將一個(gè) 64 位的 long 或 double 值的寫入分為相鄰的兩個(gè) 32 位的寫入對(duì)于 JVM 的實(shí)現(xiàn)來說是很方便的。為了性能上的考慮,JVM 的實(shí)現(xiàn)是可以決定采用原子寫入還是分為兩個(gè)部分寫入的。

如果可能的話,我們鼓勵(lì) JVM 的實(shí)現(xiàn)避開將 64 位值的寫入分拆成兩個(gè)操作。我們也希望程序員將共享的 64 位值操作設(shè)置為 volatile 或者使用正確的同步,這樣可以提供更好的兼容性。

目前來看,64 位虛擬機(jī)對(duì)于 long 和 double 的寫入都是原子的,沒必要加 volatile 來保證原子性。

來源:https://javadoop.com/post/Threads-And-Locks-md

整理:黎杜

1.?JMH - Java 微基準(zhǔn)測(cè)試工具

2.?JWT 和 JJWT,別再傻傻分不清了!

3.?美團(tuán)一面:兩個(gè)有序的數(shù)組,如何高效合并成一個(gè)有序數(shù)組?

4.?你用什么軟件做筆記?

最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

謝謝支持喲 (*^__^*)

瀏覽 44
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)
評(píng)論
圖片
表情
推薦
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 日逼视频免费| 亚洲区中文字幕| 国内综合久久| 亚洲天堂自拍| 特黄aaaaaaaa真人毛片| 91肏屄视频| 久久先锋| 精品久久久999| 激情五月天婷婷| 日批网站在线观看| 九一国产| 99精品视频免费在线观看| 特级西西444WWW高清大视频| 久久久久9999| 38t6电影网logo高清图片| 国产Aⅴ| 九九久久免费视频| 欧美男女操逼视频| 337P大胆粉嫩噜噜噜| 免费播放片色情A片| 中文字幕在线观| 欧美成人精品欧美一级| 东京热精品视频| 亚洲AV久久无码| 91成人久久| 无码一区三区| 欧美你懂的| 天天视频色| 日本成片网| 性饥渴熟妇乱子伦| 99国产精品久久久久久久| 影音先锋av资源在线| 亚欧av无码| 日韩操B视频| 久久精品秘一区二区三免费| 最近中文字幕中文翻译歌词| 亚州AV在线| 毛片一区| 你懂的视频在线观看| 欧美成人手机在线| 亚洲欧美天堂| www.97色色| 91丨九色丨熟女老版| 69成人精品| 久久久高清无码| 久久国产精品在线| 亚洲天堂一级片| 天天综合91| 国产黄色在线免费观看| 久草热视频| 草草浮力院| 日韩小视频+国产| 日韩无码波多野结衣| 五月激情婷婷基地| av福利电影在线| 日本精品人妻无码77777| 17c.白丝喷水自慰| 日韩在线视频免费| 青榴社区| 俺来也俺去也www色官网| 在线观看视频国产| 在线视频A| yjizz国产| 欧美大胆视频| 欧美成人一区二区三区片| 成人av黄色三级片在线观看| 婷婷午夜| 草逼com| 蜜臀999| 亚洲视频免费在线| 巜痴漢電車~凌脔版2| 2026国产精品视频| 超碰伊人大香蕉| 尤物视频在线观看视频| 国产乱叫456在线| 日本欧美成人片AAAA| 亚洲高清福利| 五月色综合网| 国产美女精品久久AV爽| 日韩欧美国产| 日韩在线小视频| 男女激情网站| 91久久国产| 日韩高清一区二区| 亚洲无码黄片| 国产综合久久久7777777 | 欧美色色网站| 亚洲视频a| 色九九九九| 欧美性爱视频免费观看| 黄色视频在线免费观看网站| 91AV在线免费观看| 黑人乱伦| 91精品丝袜久久久久久| 在线观看日韩精品| 日本无码视频在线| 狠狠2021| 亚洲日韩黄色| 日本人妻在线观看| 免费无码毛片一区二区A片| 色播国产成人AV| 日韩欧美大片在线观看| 无码波多野结衣| 先锋影音资源一区| 无码人妻一区二区三一区免费n狂飙| 深爱五月天| 午夜资源站| 五月丁香色色网| 欧美日韩中文在线观看| 16一17女人毛片| 日日擼夜夜擼| 97免费在线视频| 黑人粗大无码| 精品国产免费无码久久噜噜噜AV | 99综合在线| 日韩中文字幕久久| 天堂网av在线| 中文字幕人妻日韩在线| 亚洲va国产va天堂va久久| 囯产精品久久久久久久久久| 成人一级视频| 欧美性爱一区二区| 国产精品免费在线| 亚洲成人中文字幕| 午夜一级性爱片| 中文字幕福利电影| 成人福利影视| 午夜视频99| 欧美成人性色欲影院| 久久六月天| 国产精品51麻豆cm传媒| 北条麻妃A片在线播放| 91香蕉在线看| 少妇人妻在线| 欧美性猛交XXXXⅩXX| 久久国产亚洲| 91精品无码一区二区| 麻豆一区在线观看| 欧美成人精品欧美一级| 337p西西人体大胆瓣开下部| 国产一级a毛一级a毛观看视频网站 | 99er视频| 一道本久久| 在线无码免费| 在线无码| 婷婷开心色四房播播免费| 淫色综合网| 专肏老妇人大逼| 中文字幕日韩乱伦| 18禁看网站| 仓井空一区| 亚洲精品秘一区二区三区在线观看 | 色天使亚洲| 五月丁香天堂网| 中日韩一级片| 日本免费一区二区三区| 久久久婷婷| 亚洲无码视频在线观看高清| 亚洲美女视频在线| 五月丁香色色网| 91爱爱爱爱| 国产最新av| 97人人人人人人| 五月天综合| 国产欧美精品一区二区色综合 | 臭小子晚上让你爽个够视频| 欧美理论片在线观看| 亚洲日韩中文字幕在线| 久久99九九| 视频一视频二在线视频| 国产一级免费观看| 久久久久黄| 国产黄片一区二区| 精品伊人| 丰满岳乱妇一区二区三区| 欧美第1页| 免费观看无码视频| 欧美国产日韩综合在线观看170| 婷婷五月999| 亚洲视频在线免费| 天堂网婷婷| 成年人黄色视频在线观看| 人妻天堂| www.91久久| 国产你懂的| 天天日天天操天天| 国产日韩欧美成人| 无码人妻蜜桃| www三级片| 亚洲成人三级| 精品AV| 99九九网| 99久久综合国产精品二区| 少婦揉BBBB揉BBBB揉| 大香蕉天天操| 大香蕉国产精品视频| 不卡免费视频| 国产三级毛片| 色天堂视频在线观看| re久久| 91人妻日韩人妻无码| 欧美亚洲动漫| 日日摸日日碰| 综合婷婷| 手机在线看片av| 日韩国产免费| 97无码精品人妻一区二区三区| 俺去俺来也www色官网cms| 日韩中文字幕无码| 亚洲一区中文字幕成人在线| 亚洲无码高清电影| 91麻花| 国产男女啪啪视频| 国产成人免费在线观看| 又a又黄高清无码视频| 99AV| 日本暖暖视频| 午夜久久久久久久久久久久91 | 狼人社區91國產精品| 日日騒av无码| 在线观看免费一区| 在线视频日本| 小h片在线观看| 少妇三区| 俺来也俺去www色情网| 精品国产重口乱子伦| 91精品久久久久久粉嫩| 久久精品一区二区| 操b国产| 黄色A级视频| 欧美色国| 五月综合激情| 亚洲成人福利在线| 好好日视频| 色色播| 草久在线视频| gogogo高清在线观看免费直播中国 | 99自拍视频| 日本无码嫩草一区二区| 日韩aaaa| 日本国产欧美| 毛片91| 久久久久久久久久久高清毛片一级 | 影音先锋乱伦电影| 热久久在线观看| 色婷婷一级A片AAA毛片| 黄片一区| 伊人成人网视频| 综合偷拍| 久久亚洲AV| 撸撸视频| 欧美AⅤ视频| 成人三级毛片| 91精品久久久久| 亚洲视频网址| 日韩在线免费播放| 久久穴| 91人妻人人澡人人爽人人精品 | 久草视频福利| 亚洲高清无码久久| 翔田千里无码免费播放| 中文一区| 欧美精品久久久久久久多人混战| 国产美女被| 久久大鸡巴| 国产精品成人午夜福利| 欧美久久国产精品| 在线无码中文字幕| 三级网站大全| 久久久久久97电影院电影院无码 | 99久久久99久久91熟女| 日日夜夜草| 成人日皮视频| 成人免费版欧美州| 人人操人人操人人操人人操人人操| 波多野结衣视频免费在线观看| 国产无套内射在线观看| 欧美日韩视频在线| www在线视频| 18岁成人毛片| 日韩一区二区三区在线观看| 日韩在线综合网| 影音先锋av资源在线| 色老板网站| 国产wwwww| 欧美成人超碰| 黄色日本视频| 操碰在线观看| www.俺也去| 黄色毛片网站| 九色丨蝌蚪丨老版熟女| 国产日本在线| 久色视频福利| 91久久无码一区人妻A片蜜桃| 91精品国产日韩91久久久久久| 人人妻人人澡| 国产小黄片在线| 青青草伊人网| 色婷婷久久久久swag精品| 日韩精品丰满无码一级A片∴ | 亚洲成人免费视频| 婷婷五月伊人| 俺来也俺也啪WWW色| 狠狠干综合| 99久久亚洲精品日本无码| 91人妻人人澡人人爽人人精| 91成人片| 亲子伦一区二区三区观看方式| 欧美成人三级在线观看| 97人妻碰碰中文无码久热丝袜| 免费版成人久久幺| 无码av在线播放| 亚洲a在线视频| WWW久久| 超碰中文字幕| 亚洲日逼网站| 免费成人黄色网址| 99精品视频北条麻妃国产版| 免费三级毛片| 中文字幕在线视频观看| 无码一区二区区| 精品国产一区二区三区久久久蜜月| 久久人精品| 韩国一区二区三区| 国产成人视频在线观看| 中文字幕免费毛片| 67194熟女| 日本欧洲三级| 激情成人五月天| 亚洲欧美色图| 国产亲子乱婬一级A片借种| 中文av在线播放| 3D动漫精选啪啪一期二期三期| 国产传媒精品| 无码中文字幕| 婷婷丁香色五月| 日韩视频一区二区| 无码国产99精品久久久久网站| 成人AV中文解说水果派| 手机AV免费| 日韩无码性爱视频| 91在线视频| 日本亚洲中文字幕| 99久久综合| 国产在线1| 日韩在线不卡视频| 91插插网| 特级黄色视频| 无码精品人妻一区二区欧美| 亚洲一区二区三区视频| 亚洲情免| 精品无码人妻一区二区三区| 成人毛片网站| 九九热这里有精品| 精品人妻中文字幕| 超碰成人欧美| 九九色视频| 人人射人人射| 日韩久久网| 69AV免费视频| 东京热AV在线| 丁香五月激情啪啪| 影音AV| 一级婬片A片AAAAA毛片| 七十路の高齢熟妇无码| 欧美怕怕| 黄色视频网站在线免费观看| 日无码在线| 巜痴漢電車~凌脔版2| 亚洲人人妻| 自拍三级片| 久久澡| 在线看片你懂的| 亚洲超级高清无码第一在线视频观看| 亚洲高清无码在线播放| 综合狠狠| 无码免费视频在线观看| 午夜无码精品| 91无码电影| 99热播在线| av在线免费播放| 91成人久久| 亚洲三级片在线观看| 天堂av在线免费观看| 欧美日日| 91热久久| 性爱视频91| 中文字幕Av在线| 美女毛片网站| 夜夜操天天操| 成人性爱视频在线| 高清无码人妻| 日韩在线观看中文字幕| 国产久久视频| 黄网站在线播放| 一区二区三区视屏| 五月在线视频| 欧美日韩三级在线| 国产精品国产精品国产专区不片| 亚洲一级a片| 日韩三级片网站| 五月停亭六月,六月停亭的英语 | 天堂俺去俺来也www久久婷婷| 91白浆肆意四溢456| 欧美又粗又大| 三级无码视频在线观看| 欧美性爱在线视频| 天天日天天干天天草| 亚洲欧美成人在线| 黄色精品网站| 黄色福利网址| 爆乳尤物一区二区三区| 欧美在线视频你懂的| 五月丁香欧美| 伊大香蕉| 色高清无码免费视频| 免费看黄色一级片| 日本www视频| 一本一道久久| 国产免费久久久| www.91av| 九色PORN视频成人蝌蚪自拍 | 波多野结衣大战黑人| 褒姒AV无玛| 国产精品日韩无码| 国产亚洲婷婷| 免费a视频| 日比视频网站| 国产操女人| 性无码一区二区| 亚洲AV一级| 亚洲无码三级| 天堂网在线观看| 小早川怜子精品一区二区| 亚洲中文字幕码mv| 四川少BBB搡BBB爽爽爽| 成人国产精品秘在线看| 日韩三级成人| 2021国产精品视频| 91视频亚洲| 四川BBBBBB搡BBBBB| 国产精品色呦呦| 国产精品欧美7777777| 日本成人电影一区二区三区| 美女视频一区二区三区| 人人操久久| 黄色综合网站| 色一区二区| 日韩精品人妻无码| 超碰永久| 黄色福利| 各国熟女HD合集| 亚洲中文字幕免费观看视频| 蜜臀色欲AV无码人妻| 嫩BBB| 欧美一级电影| 插菊综合网| 成人视频你懂的| 亚洲性爱一区二区三区| 91天堂| 免费无码一级A片大黄在线观看| 天天色小说| 日本黄色视频在线播放| 久久久久99精品成人片三人毛片 | 欧美亚洲精品在线| 人妻精品一二三| 一区二区三区中文字幕| 大香蕉亚洲在线| 午色婷婷国产无码| 韩国无码一区二区三区| 成功精品影院| 欧美aaa| 先锋影音av在线| 亚洲色射| 欧美老妇BBBBBBBBB| GOGO人体做爰大胆视频| 国产精品一区二区免费| 国产精品久久久久久久久免费无码 | 成人手机AV| 91狠狠| Av毛片| 大香蕉在线99| 亚洲中文字幕AV| 丰满岳乱妇一区二区三区全文阅读 | av在线资源网站| 亚洲爱爱视频| 翔田千里在线播放| 成年人免费看视频| 国产一级在线| 国产精品无码天天爽视频| 欧美在线一级片| 日韩免费在线视频观看| 婷婷伊人綜合中文字幕| 夜夜操夜夜骑| 天天草网| 91精品人妻| 天堂操逼| 亚洲永久免费精品| 444444在线观看免费高清电视剧木瓜一| 中文字幕h| 黄色AV免费在线观看| 午夜亚洲无码| 99热碰碰热| 爱爱无码| 久草热视频| 久久亚洲精品视频| 成人网站视频在线免费观看| 国产一级特黄A片| 人妻丝袜无码视频专区| 玖玖婷婷| 亚洲三级网站在线观看| 91精品久久香蕉国产线看观看 | 久草青| 中文字幕av网站| 欧美怡春院| 亚洲日韩在线观看视频| 免费看黄的网站在线观看| 五月天色婷婷丁香| 空姐白洁| 91最新网址| 黄色电影中文字幕| 无码一区二区黑人猛烈视频网站| 米奇电影777无码| 午夜aaa| 黄片午夜| 中文字幕高清| 亚洲视频偷拍| 一区二区三区国产视频| 成人无码区免费AV毛片| 亚洲精品色| 日韩在线观看中文字幕| 五月婷婷中文版| 天天爽夜夜爽夜夜爽精品视频| 无码一二三四| 亚洲AV成人无码精品| 欧美性爱第四页| 丝袜美女足交| 午夜福利片| 国产精品无码在线| 夫妻-ThePorn| 日韩在线观看免| 久久99精品久久久久婷婷| 91在线无码| 日本親子亂子倫XXXX50路| 精品中文字幕在线| 在线日韩国产| 狠狠躁日日躁夜夜躁A片视频| 91在线资源| 一级A片免费视频| 伊人精品A片一区二区三区| 超碰97人人爱| 色图插插插| 国产三级片在线观看| 日本黄色小视频| 在线免费看黄色视频| 欧美性爱18| 国产婬片一级A片AAA毛片AⅤ| 午夜福利不卡视频| 欧美一级黄色电影| 亚洲性爱影院| 亚洲AV国产| 国产一区免费观看| 自拍偷拍成人视频| 熟女少妇一区二区三区| 五月六月丁香| 探花熟女| 中文字幕人妻精品一区| 女人的天堂AV| 亚洲另类色图| 中文字幕一区二区三区日本在线 | 亚洲久久在线| 欧美操逼在线观看| 日逼高清视频| se99av| 中文字幕在线观看一区| 亚洲综合一区二区| 国产日韩欧美一区| 丰满人妻一区二区三区四区54| 日韩无码系列| 99er这里只有精品| 国产无套在线观看| 亚洲无码综合| 亚洲人在线| 黄色理论片| 国产主播第一页| 少妇人妻在线| 色色网站视频| 91丨牛牛丨国产人妻| 国产成人精品视频| AV天堂亚洲| 中文字幕第2页| 精品欧美一区二区三区久久久| 日韩一区二区三区无码| 亚洲视频天天射| 99热思思| 国产精品HongKong麻豆| 久久久精品人妻| 日韩福利在线| jizz在线观看| 国产91精品久久久天天| 欧美性爱XXXX黑人XYX性爽| 免费观看黄片网站| 国产精品色呦呦| 亚洲无码激情在线| 成人大香蕉| 一本一道久久a久久精品蜜桃| AV女人天堂| 波多野成人无码精品视频| 十八禁黄网站| 嫩BX区二区三区的区别| 99色婷婷| 美女av网站| 人人色人人草| 久久午夜无码鲁丝午夜精品| 亚洲人气无码AV| 国产精品999| 91精品久久久久久久| 91天堂| 欧美日韩在线一区| 亚洲欧美v| 日本操逼片| 天堂8在线| 亚洲色色色| 国产ts| 日本免费版网站nba| 亚洲成人精品一区| 超碰在线免费播放| 性猛交╳XXX乱大交| 日韩人妻电影| 人人狠狠综合婷婷| 一区二区三区欧美| 中文字幕在线视频观看| 婷婷天堂站| 国产人妖在线| 日皮视频免费| www.国产精品| 在线观看视频黄| 狼人狠狠干| 久久久精品免费| 高清无码三级片在线观看| 亚洲色无码| 久久久久久久久久久国产| 一区二区三区日本| 4438成人网站| 人人看人人插| 好男人一区二区三区在线观看 | 99久99| 狼友视频在线看| 伊人性视频| 黄色录像一级带| 青草五月天| 青吴乐大香蕉| 欧美日韩A片欧美日| 成人av网站在线播放| 在线观看av网站中文字幕| 亚洲天堂在线观看视频| 国产AV福利| 激情99| 91香蕉视频| 在线一区二区三区| 人妻人人操| 国产福利在线视频| 97精品人妻| 91精品国产成人观看| 超碰中文字幕| 97精品人妻| 一区二区三区四区在线| 中文字幕成人| 日本爱爱视频| 中文字幕免费在线看一区七区| 无码av无码AV| 德美日三级片在线观看| 色人阁人妻中文字幕| 国产日本欧美韩国久久久久 | 97香蕉网| av天天看| 国产7777| 欧美一级婬片AAAA毛片| 九九精品久久| 香蕉久久网| 欧美视频免费在线观看| av电影在线观看| 青草青视频| 91亚洲成人| 91成人亚洲| 九一精品| 人人摸人人摸| 91激情电影| 国产久久免费视频| 日韩一级性爱| 亚洲超级高清无码第一在线视频观看| 婷婷在线观看免费| 国产女人水真多18毛片18精品| 中文字幕理论片| 狠狠狠狠狠狠狠狠狠狠| 翔田千里一区二区三区精品播放| 超碰国产97| 国产成人精品免费看视频| 人人干日日干| 一级黄色免费看| 99视频精品全部免费看| 天天草天天| 成人做爰黄AA片免费看三区| 啊啊啊国产| 亚洲免费视频在线播放| 日本a片| 中文无码在线| 国产一级婬片A片免费妖精视频 | 91一起草高清资源| 人操人操人操| 夜夜操天天日| 欧美黄片一区| 青青草在线播放| 一级欧美| 蜜桃av秘无码一区二区| 欧美大香蕉视频| 欧美精品在线观看| 午夜无码鲁丝片午夜精品一区二区 | 色欲AV秘无码一区二区三区| 亚洲五月丁香婷婷| 在线a免费| 国产男女AV| 久久九九国产| 成年人黄色片| 五月天成人社区| 精品一区国产探花| 国外亚洲成AV人片在线观看| 国产女人18毛片水18精品软件| 日本黄在线看| www亚洲视频| 日本中文视频| 欧美精品成人在线| 香蕉91视频| 日本成人视频| 成人片成人片| 二区三区不卡| 97人操| 夜夜干天天操| 久久艹久久| 麻豆av在线观看| 人善交精品一区二区三区| 国产成人秘免费观看一区二区三区| 日韩中文字幕视频| 国产欧美一区二区三区国产幕精品 | 99re99| 久久911| 亚洲成人精品一区| 翔田千里无码AV在线观看| 一本色道久久综合无码人妻软件| 一区二区三区四区高清无码 | 久热久| 无码精品一区二区三区在线观看| 波多野结衣福利视频| 韩国成人精品三级| 69xx视频| 四虎高清无码| 日韩激情网| 五月色婷婷撸| 99国产精品久久久久久久| 日韩一区二区无码视频| 久草免费在线观看视频| 国产女人18毛片水18精| 免费高清无码在线观看| 国产乱子伦日B视频| 在线观看AV91| 在线免费黄色网址| 91三级片网站| 中文字幕av第一页| 男女AV在线| 香蕉视频色| 一区二区三区精品婷婷| 18禁成人A∨片| 免费A片在线看| 中文字幕av第一页| 国产欧美一区二区三区四区| 99re视频在线观看| 国产精品怡红院有限公司| 欧美老妇BBBBBBBBB| 一区二区三区日本| 中文字幕一区二区二三区四区| 欧美亚洲性爱| 狠狠狠干| 亚洲日韩一区二区三区四区| 性爱视频99| 精品无码一区二区三区免费| 精品一区二区三区免费毛片| 天天干天天日天天干| 日韩三级在线免费观看| 国产黄色Av| 精品www| 日本色五月| 五月天色综合| 国产黄色视频在线看| 日韩伊人网| 午夜操逼视频| 麻豆传媒嫂子| 男女做爱无码| 久热免费视频在线观看| 中文字幕不卡视频| 国产性爱AV| 国产毛片久久久久久国产毛片 | 韩日高清无码| 亚洲日韩黄色| 中文字幕一区在线| 搡BBBB搡BBB搡我瞎了| 亚洲视频日韩在线观看| 性猛交AAAA片免费观看直播| 精品动漫3D一区二区三区免费版| 日本親子亂子倫XXXX50路| 国产欧美熟妇另类久久久| 无码人妻丰满熟妇精品区| av免费观看网址| 婷婷综合素质二区| 青娱乐91| 99唉撸吧视频免费| 黄色不卡视频| 国产精品揄拍100视频| 躁BBB躁BBB添BBBBBB| 黄片免费视频| 日韩av成人| 国产熟女露脸普通话对白| 3D动漫啪啪精品一区二区中文字幕 | 免费无码国产在线观看快色| 一级黄色在线观看| 欧美日韩高清丝袜| 成年人黄色电影| 婷婷av在线| 亚洲黄色无码视频| 亚洲秘无码一区二区三区电影| 精品91视频| 欧美精产国品一二三| www.wuma| 波多野结衣在线观看一区二区| 亚洲成人AV在线观看| 国产激情视频在线观看| 免费日韩AV| 成年人免费视频在线观看| 成人毛片在线| 操逼91小视频| 毛片无遮挡| 91AV无码| 五月婷婷影院| 午夜AV在线播放| 韩国免费一级a一片在线播放| 人妻少妇无码精品| 黄色三级网站| 黄色小视频在线| 亚洲性天堂| 500部大龄熟乱4K视频| 波多野结衣99| 一级欧美黑人大战白妞| 丁香五月婷婷综合| 午夜五月天| 91三级片在线观看| 91在线无码精品秘国产| 日韩人妻精品无码| 黄色视频在线观看大全| 久久久久无码国产精品不卡| 大鸡巴在线观看| 午夜成人黄色| 精品人妻一区二区三区阅读全文 | 亲子伦一区二区三区| 中文字幕免费在线观看| 蜜桃久久精品成人无码AV| 成年人在线观看视频网站| www深夜成人a√在线| 91久久久久久久久| 91无码秘蜜桃一区二区三区-百度 精品人妻一区二区三区在线视频不卡 | 成人精品网| 色色激情五月天| 久久精品国产亚洲| 99re2| 亚洲中文无码电影| 日韩精品五区| 久久九九免费视频| 国产不卡在线视频| 色五月视频| 黄色的视频网站| 无码人妻丰满熟妇精品| 亚洲激情AV| 国产成人综合亚洲| 欧美va在线| 少妇大战28厘米黑人| 无码高清18| 中文字幕一区二区二三区四区| 高潮视频在线观看| 亚洲乱码在线| 中文无码一区二区三区| 亚洲天堂中文字幕| 无码AV电影在线观看| 欧美成人第一页| 韩剧《邻居的妻子》电视剧| 特黄一级片| 成人av一区| 国产乱婬片视频| 免费久草视频| 少妇搡BBBB搡BBBB毛多多| 亚洲码AV波多野| 青青草免费观看视频| 日韩中文视频|