两个例子详解并发编程的可见性问题和有序性问题,通过volatile保证可见性和有序性以及volatile的底层原理——缓存一致性协议MESI和内存屏障禁止指令重排
1. 并發(fā)編程的可見(jiàn)性問(wèn)題
2. 并發(fā)編程的有序性問(wèn)題
3. 使用volatile關(guān)鍵字解決可見(jiàn)性問(wèn)題
4. 可見(jiàn)性問(wèn)題的本質(zhì)——緩存不一致
因?yàn)閏pu執(zhí)行速度很快,但是內(nèi)存執(zhí)行速度相對(duì)于CPU很慢,那么每次從內(nèi)存取數(shù)據(jù)就拖慢了CPU的執(zhí)行效率。實(shí)際上,在CPU和內(nèi)存中有一層緩存,程序執(zhí)行時(shí)在緩存操作數(shù)據(jù),那么另一個(gè)緩存修改的數(shù)據(jù)是不會(huì)更新到當(dāng)前程序緩存中的,也就出現(xiàn)了緩存不一致問(wèn)題,也就出現(xiàn)了可見(jiàn)性問(wèn)題。簡(jiǎn)易內(nèi)存模型圖如下。
5. 有序性問(wèn)題的本質(zhì)——指令重排
因?yàn)镃PU或者編譯器為了優(yōu)化程序指令,會(huì)對(duì)沒(méi)有依賴(lài)關(guān)系的程序指令進(jìn)行重排。對(duì)于單線程來(lái)說(shuō),指令沒(méi)有依賴(lài)關(guān)系,重排沒(méi)有問(wèn)題,但如果是并發(fā)編程,那么指令重排是有影響的。
6. volatile是怎么解決可見(jiàn)性、有序性問(wèn)題的呢?也就是怎么解決緩存不一致和禁止指令重排的呢?
6.1 緩存不一致問(wèn)題:存在該問(wèn)題是某個(gè)線程只修改自己緩存的數(shù)據(jù),沒(méi)有將數(shù)據(jù)刷新到內(nèi)存中,或者另一個(gè)線程只從自己的緩存讀取數(shù)據(jù),沒(méi)有從內(nèi)存中取數(shù)據(jù),造成了數(shù)據(jù)“不可見(jiàn)”。如果有一種協(xié)議,當(dāng)某個(gè)線程修改緩存數(shù)據(jù)后,數(shù)據(jù)會(huì)被刷新到內(nèi)存中,同時(shí)讓其他線程的緩存數(shù)據(jù)失效,重新從內(nèi)存讀取,那么就解決緩存不一致問(wèn)題了。這個(gè)協(xié)議就是MESI緩存一致性協(xié)議,MESI表示緩存數(shù)據(jù)(通過(guò)緩存行存儲(chǔ),64個(gè)字節(jié))的四種狀態(tài)字母縮寫(xiě),四種狀態(tài)如下,當(dāng)觸發(fā)了MESI協(xié)議,就能保證數(shù)據(jù)一致,也就是保證了數(shù)據(jù)的可見(jiàn)性。volatile就是通過(guò)觸發(fā)MESI協(xié)議來(lái)保證數(shù)據(jù)可見(jiàn)的。
① Modified:數(shù)據(jù)只在當(dāng)前線程的緩存中,且緩存數(shù)據(jù)與內(nèi)存數(shù)據(jù)不一致。
② Exclusive:數(shù)據(jù)只在當(dāng)前線程的緩存中,且緩存與內(nèi)存數(shù)據(jù)一致。
③ Shared:數(shù)據(jù)存在于多個(gè)線程的緩存中,且緩存數(shù)據(jù)與內(nèi)存一致。
④ Invalid:數(shù)據(jù)無(wú)效。
6.2 禁止指令重排:對(duì)于指令重排序,我們?cè)O(shè)置某些規(guī)則,當(dāng)CPU或編譯器檢測(cè)到指令存在這些規(guī)則,那么不進(jìn)行指令重排,即可解決有序性問(wèn)題。內(nèi)存屏障對(duì)于volatile數(shù)據(jù)的讀或者寫(xiě)操作時(shí),會(huì)加入對(duì)應(yīng)內(nèi)存屏障來(lái)保證指令的有序性。具體有哪些內(nèi)存屏障,以及內(nèi)存屏障為什么是那樣的設(shè)置,這里就不多敘述了,volatile寫(xiě)之前有StoreStore屏障,之后有StoreLoad屏障(萬(wàn)能屏障,兼具其它三種屏障功能);volatile讀之后有LoadStore和LoadLoad屏障。
總結(jié)
以上是生活随笔為你收集整理的两个例子详解并发编程的可见性问题和有序性问题,通过volatile保证可见性和有序性以及volatile的底层原理——缓存一致性协议MESI和内存屏障禁止指令重排的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java——自己实现基础的线程池及带有任
- 下一篇: 为什么不使用volatile,其它线程也