<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          分享一道阿里 Java 并發(fā)面試題 (詳細分析篇)

          共 2604字,需瀏覽 6分鐘

           ·

          2019-05-30 01:28

          我個人一直認為:網絡、并發(fā)相關的知識,相對其他一些編程知識點更難一些,主要是不好調試并且涉及內容太多 !

          所以今天就取一篇并發(fā)相關的內容分享下,我相信大家認真看完會有收獲的。

          大家可以先看看這個問題,看看這個是否有問題呢? 那里有問題呢?

          J41.jpg

          如果你在這個問題上面停留超過5s的話,那么表示你對這塊某些知識還有點模糊,需要再鞏固下,下面我們一起來分析下!

          1. 結論

          多線程并發(fā)的同時進行set、get操作,A線程調用set方法,B線程并不一定能對這個改變可見!!!

          2. 分析

          這個類非常簡單,里面有一個屬性,有2個方法:get、set方法,一個用來設置屬性值,一個用來獲取屬性值,在設置屬性方法上面加了synchronized。

          隱式信息:多線程并發(fā)的同時進行set、get操作,A線程調用set方法,B線程可以里面感知到嗎???

          說到這里,問題就變成了synchronized在剛剛說的上下文下面能否保證可見性!!!

          3. 關鍵詞synchronized的用法


          • 指定加鎖對象:對給定對象加鎖,進入同步代碼前需要獲得給定對象的鎖。



          • 直接作用于實例方法:相當于對當前實例加鎖,進入同步代碼前要獲得當前實例的鎖。



          • 直接作用于靜態(tài)方法:相當于對當前類加鎖,進入同步代碼前要獲得當前類的鎖。


          synchronized它的工作就是對需要同步的代碼加鎖,使得每一次只有一個線程可以進入同步塊(其實是一種悲觀策略)從而保證線程之間得安全性。

          從這里我們可以知道,我們需要分析的屬于第二類情況,也就是說多個線程如果同時進行set方法的時候,由于存在鎖,所以會一個一個進行set操作,并且是線程安全的,但是get方法并沒有加鎖,表示假如A線程在進行set的同時B線程可以進行get操作。并且可以多個線程同時進行get操作,但是同一時間最多只能有一個set操作。

          4. Java 內存模型 happens-before原則

          JSR-133 內存模型使用 happens-before 的概念來闡述操作之間的內存可見性。在 JMM 中,如果一個操作執(zhí)行的結果需要對另一個操作可見,那么這兩個操作之間必須要存在 happens-before 關系。這里提到的兩個操作既可以是在一個線程之內,也可以是在不同線程之間。

          與程序員密切相關的 happens-before 規(guī)則如下:

          • 程序順序規(guī)則:一個線程中的每個操作,happens-before 于該線程中的任意后續(xù)操作。

          • 監(jiān)視器鎖規(guī)則:對一個監(jiān)視器的解鎖,happens-before 于隨后對這個監(jiān)視器的加鎖。

          • volatile 變量規(guī)則:對一個 volatile 域的寫,happens-before 于任意后續(xù)對這個 volatile 域的讀。

          • 傳遞性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。

          注意,兩個操作之間具有 happens-before 關系,并不意味著前一個操作必須要在后一個操作之前執(zhí)行!happens-before 僅僅要求前一個操作(執(zhí)行的結果)對后一個操作可見,且前一個操作按順序排在第二個操作之前(the first is visible to and ordered before the second)。

          其中有監(jiān)視器鎖規(guī)則:對一個監(jiān)視器的解鎖,happens-before 于隨后對這個監(jiān)視器的加鎖。這一條,僅僅只是針對synchronized的set方法,而對于get并沒有這方面的說明。

          其實在這種上下文下面一個synchronized的set方法,一個普通的get方法,a線程調用set方法,b線程并不一定能對這個改變可見!

          5. volatile

          volatile可見性

          前面happens-before原則就提到:volatile 變量規(guī)則:對一個 volatile 域的寫,happens-before 于任意后續(xù)對這個 volatile 域的讀。volatile從而保證了多線程下的可見性!!!

          volatile 禁止內存重排序

          下面是 JMM 針對編譯器制定的 volatile 重排序規(guī)則表:

          J42.jpg

          為了實現 volatile 的內存語義,編譯器在生成字節(jié)碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。

          下面是基于保守策略的 JMM 內存屏障插入策略:


          • 在每個 volatile 寫操作的前面插入一個 StoreStore 屏障。



          • 在每個 volatile 寫操作的后面插入一個 StoreLoad 屏障。



          • 在每個 volatile 讀操作的后面插入一個 LoadLoad 屏障。



          • 在每個 volatile 讀操作的后面插入一個 LoadStore 屏障。


          下面是保守策略下,volatile 寫操作 插入內存屏障后生成的指令序列示意圖:

          J43.jpg

          下面是在保守策略下,volatile 讀操作 插入內存屏障后生成的指令序列示意圖:

          J44.jpg

          上述 volatile 寫操作和 volatile 讀操作的內存屏障插入策略非常保守。在實際執(zhí)行時,只要不改變 volatile 寫-讀的內存語義,編譯器可以根據具體情況省略不必要的屏障。

          雙重檢查鎖實現單例中就需要用到這個特性!!!

          6. 模擬

          通過上面的分析,其實這個題目涉及到的內容都提到了,并且進行了解答。

          雖然你知道的原因,但是想模擬并不是一件容易的事情!,下面我們來模擬看看效果:

          1. public?class?ThreadSafeCache?{

          2. ????int?result;


          3. ????public?int?getResult()?{

          4. ????????return?result;

          5. ????}


          6. ????public?synchronized?void?setResult(int?result)?{

          7. ????????this.result?=?result;

          8. ????}


          9. ????public?static?void?main(String[]?args)?{

          10. ????????ThreadSafeCache?threadSafeCache?=?new?ThreadSafeCache();


          11. ????????for?(int?i?=?0;?i?8;?i++)?{

          12. ????????????new?Thread(()?->?{

          13. ????????????????int?x?=?0;

          14. ????????????????while?(threadSafeCache.getResult()?100)?{

          15. ????????????????????x++;

          16. ????????????????}

          17. ????????????????System.out.println(x);

          18. ????????????}).start();

          19. ????????}


          20. ????????try?{

          21. ????????????Thread.sleep(1000);

          22. ????????}?catch?(InterruptedException?e)?{

          23. ????????????e.printStackTrace();

          24. ????????}


          25. ????????threadSafeCache.setResult(200);

          26. ????}

          27. }

          效果:

          J45.jpg

          程序會一直卡在這邊不動,表示set修改的200,get方法并不可見!!!

          添加volatile 關鍵詞觀察效果

          其實例子中synchronized關鍵字可以去掉,僅僅用volatile即可。

          J46.jpg

          效果:

          J47.jpg

          代碼很快正常結束了!

          結論:?線程并發(fā)的同時進行set、get操作,A線程調用set方法,B線程并不一定能對這個改變可見!!!,上面的代碼中,如果對get方法也加synchronized也是可見的,還是happens-before的?監(jiān)視器鎖規(guī)則:對一個監(jiān)視器的解鎖,happens-before 于隨后對這個監(jiān)視器的加鎖。?只是volatile比synchronized更輕量級,所以本例直接用volatile。但是對于符合非原子操作i++這里還是不行的還是需要synchronized。



          瀏覽 111
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼网站在线 | 国产精品福利在线视频 | 久久久久久国产 | 青青草强奸视频 | 天天操天天看 |