計算機系統(tǒng)的一致性
在現代計算機操作系統(tǒng)中舟茶,多任務處理幾乎是一項必備的功能馁蒂,因為嵌入了多核處理器鲤孵,計算機系統(tǒng)真正做到了同一時間執(zhí)行若干個任務用僧,是名副其實的多核系統(tǒng)结胀。在多核系統(tǒng)中,為了提升CPU與內存的交互效率责循,一般都設置了一層 “高速緩存區(qū)” 作為內存與處理器之間的緩沖糟港,使得CPU在運算的過程中直接從高速緩存區(qū)讀取數據,一定程度上解決了性能的問題院仿。但是着逐,這樣也帶來了一個新問題,就是“緩存一致性”的問題意蛀。比如耸别,多核的情況下,每個處理器都有自己的緩存區(qū)县钥,數據如何保持一致性秀姐。針對這個問題,現代的計算機系統(tǒng)引入多處理器的數據一致性的協(xié)議,包括MOSI坑质、Synapse账磺、Firely、DragonProtocol等蠢沿。
當處理器通過高速緩存區(qū)與主內存發(fā)生交互時,對數據的讀寫必須遵循協(xié)議規(guī)定的標準匾效,用一張關系圖表示的話大概如下:
而Java的內存模型 (JMM) 可以說與硬件的一致性模型很相似舷蟀,采用的是共享內存的線程通信機制。Java內存模型
Java內存模型規(guī)定了所有的變量都存儲在主內存中面哼,每個線程擁有自己的工作內存野宜,工作內存中保存了被該線程使用的變量的主內存副本拷貝,線程只能操作自己工作內存的變量副本魔策,操作完變量后會更新到主內存匈子,通過主內存來完成與其他線程間變量值的傳遞。此模型的交互關系如下圖所示:
然而闯袒,Java的內存模型只是反映了虛擬機內部的線程處理機制虎敦,并不保證程序本身的并發(fā)安全性游岳。舉一個例子,在程序中對一個共享變量做自增操作:
i++其徙;
假設初始化的時候i=0吭历,當跑到此程序時,線程首先從主內存讀取i的值擂橘,然后復制到自己的工作內存晌区,進行i++操作,最后將操作后的結果從工作內存復制到主內存中通贞。如果是兩個線程執(zhí)行i++的程序朗若,預期的結果是2。但真的是這樣嗎昌罩?答案是否定的哭懈。
假設線程1讀取主內存的i=0,復制到自己的工作內存茎用,在進行i++的操作后還沒來得及更新到主內存遣总,這時線程2也讀取i=0,做了同樣的操作轨功,那么最終得到的結果為1旭斥,而不是2。
這是典型的關于多線程并發(fā)安全例子古涧,也是Java并發(fā)編程中最值得探討的話題之一垂券,一般來說,處理這種問題有兩種手段:
加鎖羡滑,比如同步代碼塊的方式菇爪。保證同一時間只能有一個線程能執(zhí)行i++這條程序。
利用線程間的通信柒昏,比如使用對象的wait和notify方法來凳宙。
因為本文主要是探究 JMM 和 volatile 關鍵字的知識,具體怎么實現并發(fā)處理就不做深入探討了职祷,改天看看抽個時間再寫篇博文專門講解好了氏涩。
內存模型的3個重要特征
初步了解完什么是JMM后,我們來進一步了解它的重要特征堪旧。值得說明的是削葱,在Java多線程開發(fā)中,遵循著三個基本特性淳梦,分別是原子性、可見性和有序性昔字,而Java的內存模型正是圍繞著在并發(fā)過程中如何處理這三個特征建立的爆袍。
原子性
原子性是指操作是原子性的首繁,不可中斷的。舉個例子:
String s="abc";
這個操作是直接賦值陨囊,是原子性操作弦疮。而類似下面這段代碼就不是原子性了:
i++;當執(zhí)行i++時,需要先獲取i的值蜘醋,然后再執(zhí)行i+1胁塞,相當于包含了兩個操作,所以不是原子性压语。
可見性
可見性是指共享數據的時候啸罢,一個線程修改了數據,其他線程知道數據被修改胎食,會重新讀取最新的主存的數據扰才。就像前面說的兩個線程處理i++的問題,線程1改完后沒有更新到主內存厕怜,所以線程2是不知道的衩匣。
有序性
是指代碼執(zhí)行的有序性,對于一個線程執(zhí)行的代碼粥航,我們可以認為代碼是依次執(zhí)行的琅捏,但并發(fā)中可能就會出現亂序,因為代碼有可能發(fā)生指令重排序(Instruction Reorder)递雀,重排后的指令與原指令的順序未必一致午绳。
指令重排序
編譯器能夠自由的以優(yōu)化的名義去改變指令順序。在特定的環(huán)境下映之,處理器可能會次序顛倒的執(zhí)行指令拦焚。是為指令的重排序,尤其是并發(fā)的情況下杠输。
java提供了volatile和synchronized來保證線程之間操作的有序性赎败。volatile含有禁止指令重排序的語義(即它的第二個語義),synchronized規(guī)定一個變量在同一時刻只允許一條線程對其lock操作蠢甲,也就是說同一個鎖的兩個同步塊只能串行進入僵刮。禁止了指令的重排序。
volatile關鍵字
說到了volatile鹦牛,我們就有必要了解一下這個關鍵字是做什么的搞糕。
準確來說,volatile是java提供的輕量的同步機制曼追。它有兩個特性:
保證修飾的變量對所有線程的可見性窍仰。
禁止指令的重排序優(yōu)化。
保證可見性和防止指令重排
簡單寫段代碼說明一下:
publicclassVolatileDemo{privatestaticbooleanisReady;privatestaticintnumber;privatestaticclassReaderThreadextendsThread{@Overridepublicvoidrun(){while(!isReady); System.out.println("number = "+number); } }publicstaticvoidmain(String[] args){newReaderThread().start();try{ Thread.sleep(1000); number =42; isReady =true; Thread.sleep(1000); }catch(InterruptedException e) { e.printStackTrace(); } }}
在上面的代碼中礼殊,ReaderThread只有在isReady 為 true 時才會打印出 number 的值驹吮,然而针史,真實的情況有可能是打印不出來(可能性比較小,但還是有)碟狞,因為線程ReaderThread線程無法看到主線程中對isReady的修改啄枕,導致while循環(huán)永遠無法退出,同時族沃,因為有可能發(fā)生指令重排频祝,導致下面的代碼不能按順序執(zhí)行:
number=42;isReady=true;
也就是能打印的話,number值可能是0脆淹,不是42常空。如果在變量加上volatile關鍵字,告訴Java虛擬機這兩個變量可能會被不同的線程修改未辆,那么就可以防止上述兩種不正常的情況的發(fā)生窟绷。
不能保證原子性
volatile能保證可見性和有序性,但無法保證原子性咐柜,比如下面的例子:
publicclassVolatileDemo{publicstaticvolatileinti =0;publicstaticvoidincrease(){ i++; }publicstaticvoidmain(String[] args) throws InterruptedException{ VolatileDemo test =newVolatileDemo();for(inti =0; i <10; i++) {newThread(() -> {for(intj =0; j <1000; j++) test.increase(); }).start(); }? Thread.sleep(1000); System.out.println(test.i); }}
正常情況下兼蜈,我們期望上面的main函數執(zhí)行完后輸出的結果是10000,但你會發(fā)現拙友,結果總是會小于10000为狸,因為increase()方法中的i++操作不是原子性的,分成了讀和寫兩個操作遗契。假設當線程1讀取了 i 的值辐棒,還沒有修改,線程2這時也進行了讀取牍蜂。然后漾根,線程1修改完了,通知線程2重新讀取 i 的值鲫竞,可這時它不需要讀取 i辐怕,它仍執(zhí)行寫操作,然后賦值給主線程从绘,這時數據就會出現問題寄疏。
所以,一般針對共享變量的讀寫操作僵井,還是需要用鎖來保證結果陕截,例如加上 synchronized關鍵字。
在這里我為java的同道者準備了以上相關資料批什,有需要的可以加群:810589193农曲,點擊鏈接加入群聊【Java架構學習交流群】:https://jq.qq.com/?_wv=1027&k=5deQUBl里面有阿里Java高級大牛直播講解知識點,分享知識渊季,課程內容都是各位老師多年工作經驗的梳理和總結朋蔫,帶著大家全面罚渐、科學地建立自己的技術體系和技術認知却汉!
開發(fā)人員需能夠自我激勵驯妄,主動學習新技術,并在職業(yè)生涯中給自己扣上很多帽子合砂。 繼而不斷挑戰(zhàn)自我青扔,然后更好地解決問題,這就是編程的本質翩伪。 知識很重要微猖,在某些復雜問題的情況下更是如此。在變化如此之快的IT技術領域中缘屹,知識的獲取在任何時候比我們已會的技能更為重要凛剥。
可以加群:810589193,點擊鏈接加入群聊【Java架構學習交流群】:https://jq.qq.com/?_wv=1027&k=5deQUBl我會分享一些免費的資深架構師錄制的視頻錄像:有Spring轻姿,MyBatis犁珠,Netty源碼分析,高并發(fā)互亮、高性能犁享、分布式、微服務架構的原理豹休,JVM性能優(yōu)化這些成為架構師必備的知識體系炊昆。