先來一點(diǎn)鋪墊
先看看內(nèi)存模型的概念
cpu經(jīng)常會讀取和寫入數(shù)據(jù)负懦,但是cpu的運(yùn)算速度很快,但是從主存讀寫數(shù)據(jù)的速度遠(yuǎn)遠(yuǎn)比不上cpu的
讀寫速度仙逻。所以中間就添加了一個高速緩存瞧捌。主存把數(shù)據(jù)放到高速緩存中折联,cpu從高速緩存中讀取涕癣,然后返回
給高速緩存彬祖。高速緩存再寫入主存
如何解決多線程共享數(shù)據(jù)不一致
多線程訪問共享數(shù)據(jù)如果沒有一些手段仿贬,將會出問題如臟讀纽竣。。
兩種解決方案:加鎖茧泪。緩存一致性
加鎖:在cpu讀取內(nèi)存時蜓氨。系統(tǒng)鎖住該線程,并且不準(zhǔn)其他cpu讀取數(shù)據(jù)调炬。直到該操作完成為止
緩存一致性:當(dāng)cpu發(fā)現(xiàn)操作的變量是共享變量(其他cpu中存在副本)语盈。就發(fā)出信號通知其他cpu將
該變量的緩存行設(shè)置為無效
[圖片上傳失敗...(image-fdc73c-1518589180914)]
并發(fā)編程的三問題
原子性問題
可見性問題
有序性問題:處理器并不會按照順序加載代碼(如果兩行代碼沒有依賴性)$峙荩可能會重排序刀荒,但是處理器會保證運(yùn)算結(jié)果和初始順序結(jié)果一樣
在單線程情況下是沒問題的。但是在多線程情況下就有問題了棘钞。
java內(nèi)存模型解決三問題
原子性問題:對基本數(shù)據(jù)的讀取和賦值是原子操作缠借,更多的可用synchronized和lock
可見性問題:volatile關(guān)鍵字。被volatile關(guān)鍵字修飾的變量更新后將會馬上更新到主存宜猜。其他
線程會去讀新值泼返。synchronized和lock也能保證
有序性問題:volatile能保證一點(diǎn)有序性。synchronized和lock保證有序性
happen-before原則姨拥。不能遵從happen-before绅喉,虛擬機(jī)就可以對他們隨意排序
happen-before原則
1.程序順序原則。前一個操作結(jié)果對后一個操作是可見的叫乌。
2.鎖定規(guī)則:同一個鎖的解鎖 happen-before 同一個鎖的加鎖操作
3.volatile規(guī)則:一個變量的寫發(fā)生在寫之前
4.傳遞規(guī)則:a發(fā)生在b前柴罐,b發(fā)生在c前,則a發(fā)生在c前
5.線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每個一個動作
6.線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生
7.線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測憨奸,我們可以通過Thread.join()方法結(jié)束革屠、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行
8.對象終結(jié)規(guī)則:一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始
volatile的兩層語義
1)保證了不同線程對這個變量進(jìn)行操作時的可見性,即一個線程修改了某個變量的值排宰,這新值對其他線程來說是立即可見的似芝。
2)禁止進(jìn)行指令重排序。
volatile不能保證原子性
volatile 會保證線程讀到最新的值板甘。但是是在沒有發(fā)生堵塞的情況下
如果一個自增操作党瓮,在查詢到值后,被堵塞盐类。就變成了查詢操作寞奸,也就不會無效其他cpu的緩存
其他線程就只能讀到臟數(shù)據(jù)
volatile保證一定的有序性
volatile變量保證他前面的操作都已經(jīng)進(jìn)行痕寓,后面肯定沒有進(jìn)行
volatile的原理
加入volatile的代碼會生成一個lock前綴
lock前綴指令實(shí)際上相當(dāng)于一個內(nèi)存屏障(也成內(nèi)存柵欄),內(nèi)存屏障會提供3個功能:
1)它確保指令重排序時不會把其后面的指令排到內(nèi)存屏障之前的位置蝇闭,也不會把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時硬毕,在它前面的操作已經(jīng)全部完成呻引;
2)它會強(qiáng)制將對緩存的修改操作立即寫入主存;
3)如果是寫操作吐咳,它會導(dǎo)致其他CPU中對應(yīng)的緩存行無效逻悠。