volatile和synchronized的區別

可見性(Visibility)

可見性就是指當一個線程修改了線程共享變量的值,其它線程能夠立即得知這個修改。Java內存模型是通過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值這種依賴主內存作爲傳遞媒介的方法來實現可見性的,無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區別是volatile的特殊規則保證了新值能立即同步到主內存,以及每使用前立即從內存刷新。因爲我們可以說volatile保證了線程操作時變量的可見性,而普通變量則不能保證這一點 。

原子性(Atomicity)

原子性是指在一個操作中就是cpu不可以在中途暫停然後再調度,既不被中斷操作,要不執行完成,要不就不執行。

如果一個操作時原子性的,那麼多線程併發的情況下,就不會出現變量被修改的情況。

有序性(Ordering)

Java內存模型中的程序天然有序性可以總結爲一句話:如果在本線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。前半句是指“線程內表現爲串行語義”,後半句是指“指令重排序”現象和“工作內存中主內存同步延遲”現象。

Java語言提供了volatile和synchronized兩個關鍵字來保證線程之間操作的有序性,volatile關鍵字本身就包含了禁止指令重排序的語義,而synchronized則是由“一個變量在同一時刻只允許一條線程對其進行lock操作”這條規則來獲得的,這個規則決定了持有同一個鎖的兩個同步塊只能串行地進入。

先行發生原則:

如果Java內存模型中所有的有序性都只靠volatile和synchronized來完成,那麼有一些操作將會變得很囉嗦,但是我們在編寫Java併發代碼的時候並沒有感覺到這一點,這是因爲Java語言中有一個“先行發生”(Happen-Before)的原則。這個原則非常重要,它是判斷數據是否存在競爭,線程是否安全的主要依賴。

先行發生原則是指Java內存模型中定義的兩項操作之間的依序關係,如果說操作A先行發生於操作B,其實就是說發生操作B之前,操作A產生的影響能被操作B觀察到,“影響”包含了修改了內存中共享變量的值、發送了消息、調用了方法等。

volatile和synchronized的區別:

  • volatile是線程同步的輕量級實現, 故volatile性能要比synchronized要好。
  • volatile是變量修飾符,其修飾的變量具有可見性。可見性也就是說一旦某個線程修改了該被volatile修飾的變量,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,可以立即獲取修改之後的值。
    在Java中爲了加快程序的運行效率,對一些變量的操作通常是在該線程的寄存器或是CPU緩存上進行的,之後纔會同步到主存中,而加了volatile修飾符的變量則是直接讀寫主存。而synchronized能修飾方法以及代碼塊。
  • 多線程訪問volatile不會發生阻塞,而synchronized會出現阻塞。
  • volatile可以禁止進行指令重排。指令重排是指處理器爲了提高程序運行效率,可能會對輸入代碼進行優化,它不保證各個語句的執行順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。指令重排序不會影響單個線程的執行,但是會影響到線程併發執行的正確性。程序執行到volatile修飾變量的讀操作或者寫操作時,在其前面的操作肯定已經完成,且結果已經對後面的操作可見,在其後面的操作肯定還沒有進行。
  • volatile可以保證數據的可見性,但不能保證原子性。synchronized可以保證原子性,也闊可以間接保證可見性,因爲它會將私有內存和共有內存中數據做同步。(可見性體現在:通過synchronized或者Lock能保證同一時刻只有一個線程獲取鎖然後執行同步代碼,並且在釋放鎖之前會將對變量的修改刷新到主存中)
  • 最後:volatile關鍵是變量在多個線程之間的可見性,而synchronized是解決的是多個線程訪問資源的同步性。

分析:volatile保證變量在多個線程之間的可見性,但不具備同步性,也就不具備原子性。volatile出現非線程安全的原因?

  • read和load階段:從主存複製變量到當前線程工作內存
  • use和assign階段:執行代碼,改變共享變量的值
  • store和write階段:用工作內存數據刷新到主存對應變量的值
  • 在多線程環境中,use和assign是多次出現的,但這一操作是非原子性,若內存中有count變量,也就是在read和load之後,如果內存count變量發生修改之後,線程工作內存的值已經加載,不會產生對應的變化,也就是私有內存和公共內存中的變量是不同步的,所以也就出現了非線程安全的問題。對於volatile修飾的變量,JVM只保證從主內存到線程工作內存的值是最新的。實質是:volatile解決的是變量讀時的可見性問題,但無法保證原子性,所以對於多線程的訪問同一個變量還是需要加鎖同步的。

Java多線程中提到的原子性和可見性、有序性
Java併發——線程同步Volatile與Synchronized詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章