我我的一直認爲:網絡、併發相關的知識,相對其餘一些編程知識點更難一些,主要是很差調試而且涉及內容太多 !程序員
因此今天就取一篇併發相關的內容分享下,我相信你們認真看完會有收穫的。編程
你們能夠先看看這個問題,看看這個是否有問題呢? 那裏有問題呢?安全
若是你在這個問題上面停留超過5s的話,那麼表示你對這塊某些知識還有點模糊,須要再鞏固下,下面咱們一塊兒來分析下!性能優化
多線程併發的同時進行set、get操做, A線程調用set方法,B線程並必定能對這個改變可見!!!網絡
這個類很是簡單,裏面有一個屬性,有2個方法:get、set方法,一個用來設置屬性值,一個用來獲取屬性值,在設置屬性方法上面加了synchronized。多線程
隱式信息:多線程併發的同時進行set、get操做, A線程調用set方法,B線程能夠裏面感知到嗎???架構
說到這裏, 問題就變成了synchronized在剛剛說的上下文下面可否保證可見性!!!併發
指定加鎖對象:對給定對象加鎖,進入同步代碼前須要得到給定對象的鎖。app
直接做用於實例方法:至關於對當前實例加鎖,進入同步代碼前要得到當前實例的鎖。分佈式
直接做用於靜態方法:至關於對當前類加鎖,進入同步代碼前要得到當前類的鎖。
synchronized它的工做就是對須要同步的代碼加鎖,使得每一次只有一個線程能夠進入同步塊(實際上是一種悲觀策略)從而保證線程之間得安全性。
從這裏咱們能夠知道,咱們須要分析的屬於第二類狀況,也就是說多個線程若是同時進行set方法的時候,因爲存在鎖,因此會一個一個進行set操做,而且是線程安全的,可是get方法並無加鎖,表示假如A線程在進行set的同時B線程能夠進行get操做。而且能夠多個線程同時進行get操做,可是同一時間最多隻能有一個set操做。
JSR-133 內存模型使用 happens-before 的概念來闡述操做之間的內存可見性。在 JMM 中,若是 一個操做執行的結果須要對另外一個操做可見 ,那麼這兩個操做之間必需要存在 happens-before 關係。這裏提到的兩個操做既能夠是在一個線程以內,也能夠是在不一樣線程之間。
與程序員密切相關的 happens-before 規則以下:
程序順序規則:一個線程中的每一個操做,happens-before 於該線程中的任意後續操做。
監視器鎖規則:對一個監視器的解鎖,happens-before 於隨後對這個監視器的加鎖。
volatile 變量規則:對一個 volatile 域的寫,happens-before 於任意後續對這個 volatile 域的讀。
傳遞性:若是 A happens-before B,且 B happens-before C,那麼 A happens-before C。
注意,兩個操做之間具備 happens-before 關係,並不意味着前一個操做必需要在後一個操做以前執行!happens-before 僅僅要求前一個操做(執行的結果)對後一個操做可見,且前一個操做按順序排在第二個操做以前(the first is visible to and ordered before the second)。
其中有 監視器鎖規則:對一個監視器的解鎖,happens-before 於隨後對這個監視器的加鎖。這一條,僅僅只是針對synchronized的set方法,而對於get並無這方面的說明。
其實在這種上下文下面一個synchronized的set方法,一個普通的get方法,a線程調用set方法,b線程並必定能對這個改變可見!
前面happens-before原則就提到: volatile 變量規則:對一個 volatile 域的寫,happens-before 於任意後續對這個 volatile 域的讀。 volatile從而保證了多線程下的可見性!!!
volatile 禁止內存重排序
下面是 JMM 針對編譯器制定的 volatile 重排序規則表:
爲了實現 volatile 的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。
下面是基於保守策略的 JMM 內存屏障插入策略:
在每一個 volatile 寫操做的前面插入一個 StoreStore 屏障。
在每一個 volatile 寫操做的後面插入一個 StoreLoad 屏障。
在每一個 volatile 讀操做的後面插入一個 LoadLoad 屏障。
在每一個 volatile 讀操做的後面插入一個 LoadStore 屏障。
下面是保守策略下,volatile 寫操做 插入內存屏障後生成的指令序列示意圖:
下面是在保守策略下,volatile 讀操做 插入內存屏障後生成的指令序列示意圖:
上述 volatile 寫操做和 volatile 讀操做的內存屏障插入策略很是保守。在實際執行時,只要不改變 volatile 寫-讀的內存語義,編譯器能夠根據具體狀況省略沒必要要的屏障。
經過上面的分析,其實這個題目涉及到的內容都提到了,而且進行了解答。
雖然你知道的緣由,可是想模擬並非一件容易的事情!,下面咱們來模擬看看效果:
publicclassThreadSafeCache{intresult;publicintgetResult(){returnresult; }publicsynchronizedvoidsetResult(intresult){this.result = result; }publicstaticvoidmain(String[] args){ ThreadSafeCache threadSafeCache =newThreadSafeCache();for(inti =0; i <8; i++) {newThread(() -> {intx =0;while(threadSafeCache.getResult() <100) { x++; } System.out.println(x); }).start(); }try{ Thread.sleep(1000); }catch(InterruptedException e) { e.printStackTrace(); } threadSafeCache.setResult(200); }}
效果:
程序會一直卡在這邊不動,表示set修改的200,get方法並不可見!!!
其實例子中synchronized關鍵字能夠去掉,僅僅用volatile便可。
效果:
代碼很快正常結束了!
加架構羣:705127209 領取資料,裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的資料
多線程併發的同時進行set、get操做, A線程調用set方法,B線程並必定能對這個改變可見!!! ,上面的代碼中,若是對get方法也加synchronized也是可見的,仍是happens-before的 監視器鎖規則:對一個監視器的解鎖,happens-before 於隨後對這個監視器的加鎖。 ,只是volatile比synchronized更輕量級,因此本例直接用volatile。可是對於符合原子操做i++這裏仍是不行的仍是須要synchronized。