JavaSE学习53:细说多线程之内存可见性
?一共享變量在線程間的可見(jiàn)性
? ? ? ??(1)有關(guān)可見(jiàn)性的一些概念介紹
? ? ? ??可見(jiàn)性:一個(gè)線程對(duì)共享變量值的修改,能夠及實(shí)地被其他線程看到。
? ? ? ??共享變量:如果一個(gè)變量在多個(gè)線程的工作內(nèi)存中都存在副本,那么這個(gè)變量就是這幾個(gè)線程的共享變量。所
有的變量都存儲(chǔ)在主內(nèi)存中。
? ? ? ??線程的工作內(nèi)存:每個(gè)線程都有自己獨(dú)立的工作內(nèi)存,里面保存該線程使用到的變量的副本(主內(nèi)存中該變量的
一份拷貝)。
? ? ? ??(2)數(shù)據(jù)爭(zhēng)用問(wèn)題
? ? ? ??多個(gè)線程對(duì)同一資源操作時(shí),通常會(huì)產(chǎn)生進(jìn)程,比如一個(gè)線程往消息隊(duì)列插入數(shù)據(jù),而另一個(gè)線程從消息隊(duì)列取
出數(shù)據(jù) 當(dāng)消息隊(duì)列滿時(shí),插入消息的隊(duì)列需要Sleep幾個(gè)毫秒,把時(shí)間片讓出給取消息的線程,當(dāng)消息隊(duì)列為空
時(shí),取消息隊(duì)列的線程需要Sleep幾個(gè)毫秒,把時(shí)間片讓給插入消息的線程。如果不這樣做,則會(huì)出現(xiàn)某個(gè)線程獨(dú)占
資源,最終導(dǎo)致另一個(gè)線程死等狀態(tài),會(huì)引發(fā)一些我問(wèn)題。這其中就涉及到了數(shù)據(jù)爭(zhēng)用問(wèn)題。
? ? ? ??(3)Java內(nèi)存模型(JMM)
? ? ? ??Java內(nèi)存模型(Java?Memory Model)描述了Java程序中各種變量(線程共享變量)的大訪問(wèn)規(guī)則,以及在JVM中將
變量存儲(chǔ)到內(nèi)存和從內(nèi)存中讀取變量這樣的底層細(xì)節(jié)。
? ? ? ? JMM模型的工作流程:
? ? ? ??其中有兩條規(guī)定:
? ? ? ? 1)線程對(duì)共享變量的所有操作都必須在自己的工作內(nèi)存中進(jìn)行,不能直接從主存中讀寫(xiě)。
? ? ? ? 2)不同線程之間無(wú)法直接訪問(wèn)其他線程工作內(nèi)存中的變量,線程間變量值的傳遞需要通過(guò)主存來(lái)完成。
? ? ? ??(4)共享變量可見(jiàn)性實(shí)現(xiàn)的原理
? ? ? ? 線程1對(duì)共享變量的修改要想被線程2及時(shí)看到,必須要經(jīng)過(guò)如下兩個(gè)步驟:
? ? ? ? 1)把工作內(nèi)存1中更新過(guò)的共享變量刷新到主內(nèi)存中。? ? ? ? 2)將主內(nèi)存中最新的共享變量的值更新到工作內(nèi)存2中。
? ? ? ? 我們來(lái)看一下這個(gè)流程:
? ? ? ??要實(shí)現(xiàn)共享變量的的可見(jiàn)性,必須保證兩點(diǎn):
? ? ? ? 1)線程修改后的共享變量值能夠及時(shí)從工作內(nèi)存刷新到主內(nèi)存中。
? ? ? ? 2)其他線程能夠及時(shí)把共享變量的最新值從主內(nèi)存更新到自己的工作內(nèi)存中。
? ? ? ??(5)可見(jiàn)性的實(shí)現(xiàn)方式
? ? ? ? 語(yǔ)言層面支持的可見(jiàn)性實(shí)現(xiàn)方式:
? ? ? ? 1)使用關(guān)鍵字synchronized
? ? ? ? 2)使用關(guān)鍵字volatile
? ? ? ??二synchronized實(shí)現(xiàn)可見(jiàn)性
? ? ? ??(1)Synchronized實(shí)現(xiàn)可見(jiàn)性
? ? ? ? Synchronized能夠?qū)崿F(xiàn)多線程的原子性(同步)和可見(jiàn)性。
? ? ? ? JVM關(guān)于Synchronized的兩條規(guī)定:
? ? ? ? 1)線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存中。
? ? ? ? 2)線程加鎖時(shí),將清空工作內(nèi)存中共享變量的值,從而使用共享變量時(shí)需要從主內(nèi)存中重新讀取最新的值(注
意:加鎖和解鎖需要同一把鎖)。
? ? ? ??(2)線程執(zhí)行互斥代碼的過(guò)程:
? ? ? ? 1)獲得互斥鎖
? ? ? ? 2)清空工作內(nèi)存
? ? ? ? 3)從主內(nèi)存拷貝變量的最新副本到工作內(nèi)存
? ? ? ? 4)執(zhí)行代碼
? ? ? ? 5)將更改后的共享變量的值刷新到主內(nèi)存
? ? ? ? 6)釋放互斥鎖
? ? ? ??(3)指令重排序
? ? ? ? 重排序:代碼書(shū)寫(xiě)的順序與實(shí)際執(zhí)行的順序不同,指令重排序是編譯器或處理器為了提高程序性能而做的優(yōu)化。
? ? ? ? 包括三種:
? ? ? ? 1)編譯器優(yōu)化的重排序(編譯器優(yōu)化)
? ? ? ? 2)指令級(jí)并行的重排序(處理器優(yōu)化)
? ? ? ? 3)內(nèi)存系統(tǒng)的重排序(處理器優(yōu)化)
? ? ? ? 重排序的可能:
? ? ? ??(4)as-if-serial語(yǔ)義
? ? ? ? as-if-serial含義指的是無(wú)論如何重排序,程序執(zhí)行的結(jié)果應(yīng)該與代碼順序執(zhí)行的結(jié)果一致(Java編譯器、運(yùn)行時(shí)和
處理器都會(huì)保證Java在單線程下遵循as-if-serial語(yǔ)義)。重排序不會(huì)給單線程帶來(lái)內(nèi)存可見(jiàn)性的問(wèn)題。
? ? ? ? 我們來(lái)看一段程序的例子:
? ? ? ? 單線程中程序中第1、2行的順序可以重排,但第3行不能。
? ? ? ? 多線程中程序交錯(cuò)執(zhí)行,重排序可能會(huì)造成內(nèi)存可見(jiàn)性問(wèn)題。
? ? ? ??(5)synchronized實(shí)現(xiàn)可見(jiàn)性的實(shí)例代碼:
[java]?view plaincopy print?
? ? ? ? 運(yùn)行結(jié)果:
? ? ? ? 在主線程中啟動(dòng)線程執(zhí)行讀、寫(xiě)操作。可見(jiàn)性分析:
? ? ? ? 如果線程程序正常執(zhí)行那么結(jié)果為:6
? ? ? ??第一種可能的執(zhí)行順序:
? ? ? ? 1.1—>2.1—>2.2—>1.2
? ? ? ? result的值:3
? ? ? ??第二種可能的執(zhí)行順序:
? ? ? ? 1.2—>2.1—>2.2—>1.1
? ? ? ? result的值:0
? ? ? ? 也可能有其他的情況,就不再進(jìn)行舉例。導(dǎo)致共享變量在線程間不可見(jiàn)的原因:
? ? ? ? 1)線程的交叉執(zhí)行。
? ? ? ? 2)重排序結(jié)合線程交叉執(zhí)行。
? ? ? ? 3)共享變量更新后的值沒(méi)有在工作內(nèi)存與主內(nèi)存間及時(shí)更新。
? ? ? ? 我們加入synchronized(this)同步代碼塊,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線
程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問(wèn)將會(huì)被阻塞。
? ? ? ??實(shí)現(xiàn)可見(jiàn)性改寫(xiě)后的代碼:
? ? ? ?可以在寫(xiě)線程和讀線程之間加個(gè)休眠操作,讓寫(xiě)線程執(zhí)行完,讀線程在執(zhí)行,也可以使用wait和notify來(lái)控制線程
執(zhí)行的順序。
[java]?view plaincopy print?
? ? ? ? 或者給讀操作和寫(xiě)操作的方法聲明中加關(guān)鍵字synchronized修飾:
[java]?view plaincopy print?
? ? ? ? 不可見(jiàn)原因: ? ? ? ? ? ? ? ? ? ? ? ? ?synchronized解決方案:
? ? ? ? 1)線程的交叉執(zhí)行 ? ? ? ? ? ? ? ? ? ? —>原子性
? ? ? ? 2)重排序結(jié)合線程交叉執(zhí)行 ? ? ? —>原子性
? ? ? ? 3)共享變量未及時(shí)更新 ? ? ? ? ? ? ?—>可見(jiàn)性
? ? ? ??三volatile實(shí)現(xiàn)可見(jiàn)性
? ? ? ??(1)volatile關(guān)鍵字:
? ? ? ? 1)能夠保證volatile變量的可見(jiàn)性
? ? ? ? 2)不能保證volatile變量復(fù)合操作的原子性
? ? ? ??(2)volatile如何實(shí)現(xiàn)內(nèi)存可見(jiàn)性:
? ? ? ? 深入來(lái)說(shuō):通過(guò)加入內(nèi)存屏障和禁止重排序優(yōu)化來(lái)實(shí)現(xiàn)的。
? ? ? ? 1)對(duì)volatile變量執(zhí)行寫(xiě)操作時(shí),會(huì)在寫(xiě)操作后加入一條store屏障指令。
? ? ? ? 2)對(duì)volatile變量執(zhí)行讀操作時(shí),會(huì)在讀操作后加入一條load屏障指令。
? ? ? ? 通俗地講:volatile變量在每次被線程訪問(wèn)時(shí),都強(qiáng)迫從主內(nèi)存中重讀該變量的值,而當(dāng)該變量發(fā)生變化時(shí),又會(huì)
強(qiáng)迫線程將最新的值刷新到主內(nèi)存,這樣任何時(shí)刻,不同的線程總能看到該變量的最新值。
? ? ? ? 線程寫(xiě)volatile變量的過(guò)程:
? ? ? ? 1)改變線程工作內(nèi)存中volatile變量副本的值。
? ? ? ? 2)將改變后的副本的值從工作內(nèi)存刷新到主內(nèi)存。
? ? ? ? 線程讀volatile變量的過(guò)程:
? ? ? ? 1)從主內(nèi)存中讀取volatile變量的最新值到線程的工作內(nèi)存中。
? ? ? ? 2)從工作內(nèi)存中讀取volatile變量的副本。
? ? ? ??(3)volatile不能保證volatile變量復(fù)合操作的原子性
? ? ? ? 對(duì)于下面的一段程序的使用volatile和synchronized
? ? ? ? private int number = 0; ? ?? ? ? ? ??
? ? ? ? number++;//不是原子操作 ?? ? ? ? ? ? ? ? ?
? ? ? ? 1讀取number的值 ??? ? ? ? ? ? ? ? ? ?
? ? ? ? 2將number的值加1 ? ?? ? ? ? ? ? ? ??
? ? ? ? 3寫(xiě)入最新的number的值 ?
? ? ? ??//加入synchronized,變?yōu)樵硬僮? ?
? ? ? ??synchronized(thhis){?
? ? ? ? ? ? ??number++;?
? ? ? ? }
? ? ? ??//變?yōu)関olatile變量,無(wú)法保證原子性
? ? ? ? private volatile int number = 0;
? ? ? ??(4)volatile不能保證原子性的實(shí)例代碼:
[java]?view plaincopy print?
? ? ? ? 輸出的結(jié)果基本都接近500:
? ? ? ? 解決方案:
? ? ? ??保證number自增操作的原子操作
? ? ? ? 1)使用synchronized關(guān)鍵字
? ? ? ? 2)JDK1.5以后使用ReentrantLock(java.util.concurrent.locks包下)
? ? ? ? 3)JDK1.5以后使用At.tomicIterger(java.util.concurrent.atomic包下)
? ? ? ??使用synchronized關(guān)鍵字保證原子性修改后的代碼:
[java]?view plaincopy print?
? ? ? ? ?使用ReentrantLock修改后的代碼:
[java]?view plaincopy print?
? ? ? ? ?(5)?volatile使用注意volatile適用場(chǎng)合
? ? ? ? ?要在多線程中安全使用volatile變量,必須同時(shí)滿足:
? ? ? ? ?1)對(duì)變量的寫(xiě)入操作不依賴其當(dāng)前值
? ? ? ? ?不滿足:number++ ?count=count*5等
? ? ? ? ?滿足:boolean變量、記錄溫度變化的變量
? ? ? ? ?2)該變量沒(méi)有包含在具有其他變量的不變式中。不滿足:不變式low<up
? ? ? ? ?四總結(jié)
? ? ? ? ?(1)synchronized與volatile的比較
? ? ? ? ?1)volatile比synchronized更輕量級(jí)。
? ? ? ? ?2)volatile沒(méi)有synchronized使用的廣泛。
? ? ? ? ?3)volatile不需要加鎖,比synchronized更輕量級(jí),不會(huì)哦阻塞線程。
? ? ? ? ?4)從內(nèi)存可見(jiàn)性角度看,volatile讀相當(dāng)于加鎖,volatile寫(xiě)相當(dāng)于解鎖。
? ? ? ? ?5)synchronized既能保證可見(jiàn)性,又能保證原子性,而volatile只能保證可見(jiàn)性,無(wú)法保證原子性。
? ? ? ? ?6)volatile本身不保證獲取和設(shè)置操作的原子性,僅僅保持修改的可見(jiàn)性。但是java的內(nèi)存模型保證聲明為
volatile的long和double變量的get和set操作是原子的。
? ? ? ? ?(2)補(bǔ)充1
? ? ? ? ?共享數(shù)據(jù)的訪問(wèn)權(quán)限都必須定義為private。一般是考慮安全性,對(duì)數(shù)據(jù)提供保護(hù),可以通過(guò)set()方法賦值,再
通過(guò)get()方法取值,這就是java封裝的思想。
? ? ? ? Java中對(duì)共享數(shù)據(jù)操作的并發(fā)控制是采用加鎖技術(shù)。
? ? ? ? Java中沒(méi)有提供檢測(cè)與避免死鎖的專(zhuān)門(mén)機(jī)制,但應(yīng)用程序員可以采用某些策略防止死鎖的發(fā)生。
? ? ? ? final也可以保證內(nèi)存可見(jiàn)性。
? ? ? ??(3)補(bǔ)充2
? ? ? ? 對(duì)64位(long、double)變量的讀寫(xiě)可能不是原子操作
? ? ? ? Java內(nèi)存模型允許JVM將沒(méi)有被volatile修飾的64位數(shù)據(jù)類(lèi)型的讀寫(xiě)操作劃分為兩次32位的讀寫(xiě)操作來(lái)運(yùn)行。
? ? ? ? 導(dǎo)致問(wèn)題:有可能會(huì)出現(xiàn)讀取到半個(gè)變量的情況。
? ? ? ? 解決方法:加volatile關(guān)鍵字。
? ? ? ??(4)一個(gè)問(wèn)題
? ? ? ? 即使沒(méi)有保證可見(jiàn)性的措施,很多時(shí)候共享變量依然能夠在主內(nèi)存和工作內(nèi)存間得到及時(shí)的更新?
? ? ? ? 答:一般只有在短時(shí)間內(nèi)高并發(fā)的情況下才會(huì)出現(xiàn)變量得不到及時(shí)更新的情況,因?yàn)镃PU在執(zhí)行時(shí)會(huì)很快地刷新
緩存,所以一般情況下很難看到這種問(wèn)題。
? ? ? ? 慢了不就不會(huì)刷新了。。。CPU運(yùn)算快的話,在分配的時(shí)間片內(nèi)就能完成所有工作:工作內(nèi)從1->主內(nèi)存->工作
內(nèi)存2,然后這個(gè)線程就釋放CPU時(shí)間片,這樣一來(lái)就保證了數(shù)據(jù)的可見(jiàn)性。如果是慢了話CPU強(qiáng)行剝奪該線的資
源,分配給其它線程,該線程就需要等待CPU下次給該線程分配時(shí)間片,如果在這段時(shí)間內(nèi)有別的線程訪問(wèn)共享變
量,可見(jiàn)性就沒(méi)法保證了。
from:?http://blog.csdn.net/erlian1992/article/details/51712615
總結(jié)
以上是生活随笔為你收集整理的JavaSE学习53:细说多线程之内存可见性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JavaSE学习52:细说多线程之Thr
- 下一篇: Java 程序中的多线程