java高效并發(fā)學(xué)習(xí)筆記(一)java內(nèi)存模型
學(xué)習(xí)JVM+JAVA多線程中宋雏,學(xué)習(xí)的書籍是《深入理解java虛擬機》——周志明
這里記錄一些筆記以便日后經(jīng)常學(xué)習(xí)回顧。
學(xué)習(xí)java虛擬機的并發(fā)之前,了解了物理計算機中并發(fā)的一些處理情況剪芍。計算機在處理運算任務(wù)的時候加入了高速緩存來調(diào)和處理器以及內(nèi)存之間不同數(shù)量級導(dǎo)致的耗時問題:將需要運算的數(shù)據(jù)復(fù)制到緩存中,然后進行高速運算,之后再由緩存同步回內(nèi)存中惩嘉。在java虛擬機中,定義了一種java內(nèi)存模型踢故,可以類比物理計算機的這種處理方式文黎。
主內(nèi)存與工作內(nèi)存
java虛擬機的內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存中惹苗,線程對變量的操作都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的變量耸峭。不同線程不能訪問對方的工作內(nèi)存桩蓉。
內(nèi)存之間的交互操作
根據(jù)上面所說,線程操作都是在工作內(nèi)存中劳闹,所以就存在主內(nèi)存和工作內(nèi)存之間的操作情況院究,也就是說一個變量怎么才能從主內(nèi)存中拷貝到工作內(nèi)存中,而工作內(nèi)存中的變量在線程操作完之后又是怎么寫回主內(nèi)存的本涕。java內(nèi)存模型規(guī)定了8中操作來完成上述的情況业汰,而且這8中操作都是原子性的。
lock(鎖定):作用于主內(nèi)存的變量偏友,它將一個變量標識為一條線程獨占的狀態(tài)蔬胯。
unlock(解鎖):作用于主內(nèi)存的變量,它將一個處于鎖定狀態(tài)的變量釋放出來位他,釋放后的變量才可以被其他線程鎖定氛濒。
read(讀取):作用于主內(nèi)存的變量鹅髓,它把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存舞竿,以便之后的load動作使用。
load(載入):作用于工作內(nèi)存的變量窿冯,它把read動作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中骗奖。
use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳遞給執(zhí)行引擎醒串,每當虛擬機遇到一個需要使用到變量的值的字節(jié)碼指令時將會執(zhí)行操作执桌。
assign(賦值):作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量芜赌,每當虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作仰挣。
store(存儲):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳送到主內(nèi)存中缠沈,以便隨后的write動作膘壶。
write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中洲愤。
以上8個操作是內(nèi)存模型規(guī)定的基本操作颓芭,其中read與load、store與write是成對出現(xiàn)的柬赐。
當一個線程對某一變量進行操作時亡问,如果此時有其他線程需要得到該變量的值,那么在上一線程沒有將變量同步回主內(nèi)存中時肛宋,此時后一線程所得到的該變量的值就是不確定的玛界,于是就有了volatile万矾。
volatile
volatile是java虛擬機提供的最輕量級的同步機制,該關(guān)鍵字實現(xiàn)了兩個功能:保證被修飾變量對所有線程的可見性慎框;禁止指令重排序優(yōu)化。
書中有詳細的解釋后添,這里就寫一些簡單的筆記記錄吧笨枯。
首先對于可見性的理解,volatile的可見性無法保證原子性遇西,在一下兩種情況下可以使用:運算結(jié)果并不依賴變量的當前值馅精,或者能夠保證只有單一的線程修改變量的值;變量不需要與其他的狀態(tài)變量共同參與不變約束粱檀。
對于禁止指令重排序的理解洲敢,指令重排序是指CPU可以不按照程序規(guī)定的順序?qū)⒍鄺l指令分發(fā)給相應(yīng)的電路單元處理。
原子性茄蚯、可見性压彭、有序性
關(guān)于這些特性,需要了解那些操作實現(xiàn)了這3種特性渗常。
原子性:java內(nèi)存模型的操作保證了原子性壮不。當然lock與unlock提供了更大范圍的原子性,反映到j(luò)ava代碼中就是同步塊——synchronized關(guān)鍵字皱碘。
可見性:可見性就是當一個線程修改了共享變量的值询一,其他線程能夠立刻得知這個修改。volatile就實現(xiàn)了可見性癌椿,當然實現(xiàn)了可見性的還有synchronized和final健蕊。synchronized的原理是因為執(zhí)行unlock之前必須將變量同步回主內(nèi)存。而final的原理是被修飾的字段在構(gòu)造器中一旦初始化完成踢俄,并且構(gòu)造器沒有把this的引用傳遞出去缩功,那么在其他線程中就能看到final字段的值。
有序性:在本線程中褪贵,所有的操作都是有序的掂之;在一個線程中觀察另一個線程,所有的操作都是無序的脆丁。而實現(xiàn)有序操作的方法有volatile和synchronized世舰。volatile是由于其禁止指令重排序,synchronized是因為一個變量在同一時刻只允許一條線程對其進行l(wèi)ock操作槽卫。
先行先發(fā)原則
在判斷數(shù)據(jù)是否存在競爭跟压、線程是否安全中,主要依靠了這個原則歼培。
先行先發(fā)原則是java內(nèi)存模型中定義的兩項操作之間的偏序關(guān)系震蒋,如果說操作A先行發(fā)生于B茸塞,其實就是說發(fā)生操作B之前,操作A產(chǎn)生的影響能被B觀察到查剖。比如:
i=1;
j=i;
i=2;
假設(shè)線程A中的操作i=1先行發(fā)生于線程B中的操作j=i钾虐,那么可以確定線程B的操作之后j的值一定等于1。現(xiàn)在加入C線程笋庄,依然保持線程A和線程B之間的先行發(fā)生關(guān)系效扫,而線程C出現(xiàn)在線程A和線程B操作之間,但是線程C與線程B沒有先行發(fā)生關(guān)系直砂,那么j的值是多少呢菌仁?答案不確定,可能是1也可能是2静暂,因為線程C的操作對于線程B來說可以被觀察到济丘,也可能不會。這種情況便需要保證同步洽蛀,因為不具備多線程的安全性摹迷。(時間先后順序與先行發(fā)生原則之間基本沒有太大關(guān)系,所以在衡量并發(fā)安全問題的時候不要受到時間順序的干擾辱士,一切必須以先行發(fā)生原則為準泪掀。)
java內(nèi)存模型下有一些先行發(fā)生關(guān)系,無需任何的同步器協(xié)助就可以直接使用颂碘。如果兩個操作時間關(guān)系不在此列异赫,并且無法從下列規(guī)則推導(dǎo)出來的話,它們就沒有順序性保障头岔。
程序次序規(guī)則:在一個線程內(nèi)塔拳,按照程序代碼順序,寫在前面的操作先行發(fā)生與寫在后面的操作峡竣。
管理鎖定規(guī)則:一個unlock操作先行發(fā)生與后面對同一個鎖的lock操作靠抑。
線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每一個動作。
線程終止規(guī)則:線程中所有操作都先行于對此線程的終止檢測适掰,我們可以通過Thread.join()方法結(jié)束颂碧、Thread.isAlive()的返回值等手段檢測到線程已經(jīng)終止執(zhí)行。
線程終端規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生类浪。
對象終結(jié)規(guī)則:一個對象的初始化完成先行發(fā)生于它的finalize()方法的開始载城。
volatile變量規(guī)則:對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀操作。
傳遞性:A先行發(fā)生于B费就,B先行發(fā)生于C诉瓦,則A先行發(fā)生與C。
發(fā)布于 2017-08-16
著作權(quán)歸作者所有