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