1. 程式人生 > >synchronized和volatile——原子性和可見性

synchronized和volatile——原子性和可見性

<div class="htmledit_views">
                
<h1 style="color:rgb(51,51,51);font-family:Arial;line-height:26px;text-align:center;">
<span><span style="color:rgb(0,102,0);">Java多執行緒之記憶體可見性和原子性:Synchronized和Volatile的比較</span></span></h1>
<div style="font-family:Arial;font-size:14px;line-height:26px;">
<div>
<div style="color:rgb(51,51,51);"><span style="color:rgb(0,102,0);"><span style="font-size:15px;">&nbsp; &nbsp; 【<span style="color:#336699;"><a href="http://blog.csdn.net/guyuealian/article/details/52525724" rel="nofollow" target="_blank">尊重</a></span></span><span style="font-size:15px;"><span style="color:#336699;"><a href="http://blog.csdn.net/guyuealian/article/details/52525724" rel="nofollow" target="_blank">原創,轉載請註明出處</a></span></span><span style="font-size:15px;">】http://blog.csdn.net/guyuealian/article/details/52525724</span></span></div>
<div style="color:rgb(51,51,51);"><strong><span style="color:rgb(0,102,0);"><span style="font-size:24px;"></span></span></strong></div>
<span style="color:#006600;">&nbsp; &nbsp; &nbsp;在說明Java多執行緒記憶體可見性之前,先來簡單瞭解一下Java記憶體模型。</span></div>
<div><span style="color:#006600;">&nbsp; &nbsp; &nbsp;(1)Java所有變數都儲存在主記憶體中<br>
&nbsp; &nbsp; &nbsp;(2)每個執行緒都有自己獨立的工作記憶體,裡面儲存該執行緒的使用到的變數副本(該副本就是主記憶體中該變數的一份拷貝)<br></span></div>
<div><span style="color:#006600;"><img src="https://img-blog.csdn.net/20160913144344197?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt=""><br></span></div>
<div><span style="color:#006600;">&nbsp; &nbsp;(1)執行緒對共享變數的所有操作都必須在自己的工作記憶體中進行,不能直接在主記憶體中讀寫<br>
&nbsp; &nbsp;(2)不同執行緒之間無法直接訪問其他執行緒工作記憶體中的變數,執行緒間變數值的傳遞需要通過主記憶體來完成。<br>
執行緒1對共享變數的修改,要想被執行緒2及時看到,必須經過如下2個過程:<br>
&nbsp; &nbsp;(1)把工作記憶體1中更新過的共享變數重新整理到主記憶體中<br>
&nbsp; &nbsp;(2)將主記憶體中最新的共享變數的值更新到工作記憶體2中<br></span></div>
<div><span style="color:#006600;"><img src="https://img-blog.csdn.net/20160913144450907?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt=""><br></span></div>
<div><span style="color:#006600;"><span style="color:rgb(0,102,0);font-family:Arial;font-size:14px;line-height:26px;"><strong>可見性與原子性</strong></span><br></span></div>
<div><span style="color:#006600;">&nbsp; &nbsp;可見性:一個執行緒對共享變數的修改,更夠及時的被其他執行緒看到<br>
&nbsp; &nbsp;原子性:即不可再分了,不能分為多步操作。比如賦值或者return。比如"a = 1;"和 "return a;"這樣的操作都具有原子性。類似"a += b"這樣的操作不具有原子性,在某些JVM中"a += b"可能要經過這樣三個步驟:<br>
① 取出a和b<br>
② 計算a+b<br>
③ 將計算結果寫入記憶體</span></div>
<div><span style="color:#006600;"><br></span></div>
<div><span style="font-family:Arial;font-size:14px;line-height:26px;"><span style="color:rgb(0,102,0);"><strong>(1)Synchronized:保證可見性和原子性</strong></span><br><span style="color:#006600;">&nbsp; &nbsp; Synchronized能夠實現原子性和可見性;在Java記憶體模型中,synchronized規定,執行緒在加鎖時</span><u><span style="color:#990000;">,先清空工作記憶體→在主記憶體中拷貝最新變數的副本到工作記憶體→執行完程式碼→將更改後的共享變數的值重新整理到主記憶體中→釋放互斥鎖</span></u><span style="color:#006600;">。</span></span></div>
<div><span style="font-family:Arial;font-size:14px;line-height:26px;"><span style="color:#006600;"><br></span><span style="color:rgb(0,102,0);"><strong>(2)Volatile:保證可見性,但不保證操作的原子性</strong></span><br><span style="color:#006600;">&nbsp; &nbsp; Volatile實現記憶體可見性是通過store和load指令完成的;也就是對volatile變數執行寫操作時,會在寫操作後加入一條store指令,即強迫執行緒將最新的值重新整理到主記憶體中;而在讀操作時,會加入一條load指令,即強迫從主記憶體中讀入變數的值。</span><span style="color:#006600;">但volatile不保證volatile變數的原子性,例如:</span><br></span><pre onclick="hljs.copyCode(event)"><code class="language-java hljs"><ol class="hljs-ln"><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="1"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    Private <span class="hljs-keyword">int</span> Num=<span class="hljs-number">0</span>;</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">    Num++;<span class="hljs-comment">//Num不是原子操作</span></div></div></li></ol></code><div class="hljs-button" data-title="複製"></div></pre><span style="color:#006600;">&nbsp; &nbsp; Num不是原子操作,因為其可以分為:讀取Num的值,將Num的值+1,寫入最新的Num的值。</span><br><span style="color:#006600;">&nbsp; &nbsp; 對於Num++;操作,執行緒1和執行緒2都執行一次,最後輸出Num的值可能是:1或者2</span><br><span style="color:#006600;">&nbsp; &nbsp;【</span><span style="color:rgb(0,102,0);">解釋</span><span style="color:rgb(0,102,0);">】</span><span style="color:#990000;">輸出結果1的解釋:當執行緒1執行Num++;語句時,先是讀入Num的值為0,倘若此時讓出CPU執行權,執行緒獲得執行,執行緒2會重新從主記憶體中,讀入Num的值還是0,然後執行緒2執行+1操作,最後把Num=1重新整理到主記憶體中;&nbsp;執行緒2執行完後,執行緒1由開始執行,但之前已經讀取的Num的值0,所以它還是在0的基礎上執行+1操作,也就是還是等於1,並重新整理到主記憶體中。</span><span style="color:rgb(153,0,0);">所以最終的結果是1</span></div>
<div><span style="color:rgb(0,102,0);">&nbsp; &nbsp; 一般在多執行緒中使用volatile變數,為了安全,對變數的寫入操作不能依賴當前變數的值:如Num++或者Num=Num*5這些操作。</span></div>
<div><span style="color:#006600;"><br></span><span style="color:#006600;"><strong>(3)Synchronized和Volatile的比較</strong></span><br><span style="color:#006600;">&nbsp; &nbsp; 1)Synchronized保證記憶體可見性和操作的原子性</span><br><span style="color:#006600;">&nbsp; &nbsp; 2)Volatile只能保證記憶體可見性</span><br><span style="color:#006600;">&nbsp; &nbsp; 3)Volatile不需要加鎖,比Synchronized更輕量級,並不會阻塞執行緒(volatile不會造成執行緒的阻塞;synchronized可能會造成執行緒的阻塞。)</span><br><span style="color:#006600;">&nbsp; &nbsp; 4)volatile標記的變數不會被編譯器優化,而synchronized標記的變數可以被編譯器優化(如編譯器重排序的優化).</span><br><span style="color:#006600;">&nbsp; &nbsp; 5)volatile是變數修飾符,僅能用於變數,而synchronized是一個方法或塊的修飾符。</span><br><span style="color:#006600;">&nbsp; &nbsp; &nbsp; </span><span style="color:#990000;">volatile本質是在告訴JVM當前變數在暫存器中的值是不確定的,使用前,需要先從主存中讀取,因此可以實現可見性。而對n=n+1,n++等操作時,volatile關鍵字將失效,不能起到像synchronized一樣的執行緒同步(原子性)的效果。</span><br></div>
<div><span style="color:rgb(0,102,0);"><span style="color:rgb(0,102,0);font-family:Arial;font-size:14px;line-height:26px;"><br></span></span></div>
<div><span style="color:rgb(0,102,0);font-family:Arial;font-size:14px;line-height:26px;">【參考資料】《細說Java多執行緒之記憶體可見性》</span><span style="font-family:Arial;font-size:14px;line-height:26px;"><span style="color:#990000;"><u>http://www.imooc.com/video/6775</u></span></span><span style="color:rgb(0,102,0);font-family:Arial;font-size:14px;line-height:26px;">(含視訊和程式碼)</span></div>
<div><span style="color:#006600;"><span style="color:rgb(0,102,0);font-family:Arial;font-size:14px;line-height:26px;">【相關習題】</span></span></div>
<div><span style="color:#006600;"><span style="color:rgb(0,102,0);font-family:Arial;font-size:14px;line-height:26px;"><strong>(1)下列說法不正確的是()</strong><br>
A.當兩個併發執行緒訪問同一個物件object中的這個synchronized(this)同步程式碼塊時,一個時間內只能有一個執行緒得到執行。<br>
B.當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,另一個執行緒仍然可以訪問該object中的非synchronized(this)同步程式碼塊。<br>
C.當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,其他執行緒對object中所有其它synchronized(this)同步程式碼塊的訪問不會被阻塞。<br>
D.當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,它就獲得了這個object的物件鎖。結果,其它執行緒對該object物件所有同步程式碼部分的訪問都被暫時阻塞。<br>
答案:C,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,其他執行緒對object中所有其它synchronized(this)同步程式碼塊的訪問將會被阻塞。<br><strong>(2)下面敘述錯誤的是:</strong><br>
A.通過synchronized和volatile都可以實現可見性<br>
B.不同執行緒之間可以直接訪問其他執行緒工作記憶體中的變數<br>
C.執行緒對共享變數的所有操作都必須在自己的工作記憶體中進行<br>
D.所有的變數都儲存在主記憶體中<br>
答案:B,不同執行緒之間無法直接訪問其他執行緒工作記憶體中的變數<br></span></span></div>
</div>
            </div>