volatile和synchronized的區別

概述

在Java中,爲了在多線程環境讀寫數據時保證數據的一致性,可以採用兩種方式:線程同步和使用volatile關鍵字。

線程同步

資源共享的兩個原因是資源緊缺和共建需求。線程共享CPU是從資源緊缺的維度考慮,而多線程共享變量通常是從共建需求的維度考慮的。在多個線程對同一個變量進行寫操作時,如果操作沒有原子性,就可能產生髒數據。所謂原子性操作,便是一系列不可分割的操作指令,要麼全部執行,要麼全部都不執行。如果每個線程的修改都是原子性的操作,那麼將不存在線程同步問題。線程同步就是解決
如何在線程之間按某種機制協調先後次序執行的問題。當有一個線程需要對內存中的數據進行修改時,其他線程都不可以對該數據進行修改,直到該線程修改操作完成。實現線程同步的方式有很多,比如同步方法、鎖、阻塞隊列等。

可見性

可見性是指某線程修改共享變量的指令對其他線程來說都是可見的,它反映的是指令執行的實時透明度。每個線程都有獨佔的內存區域如操作棧、本地變量表等。線程本地內存保存了引用變量在堆內存中的副本,線程對變量的所有操作都是在本地區域中進行的,執行結束後再同步到堆內存中去。在這個過程中會產生一個時間差,在這個時間差內,該線程對副本的操作對於其他線程都是不可見的。

synchronized關鍵字

synchronized提供了兩種主要特性:互斥性和可見性。

互斥性實現:互斥即一次只允許一個線程持有某個特定的鎖。換句話說,如果將鎖加在某個變量上,則每次只有一個線程能夠使用該共享數據,直到該線程使用完纔會將該共享數據釋放,供其它線程使用。

可見性實現:線程在得到鎖時讀入副本,釋放時寫回內存。

volatile關鍵字

volatile修飾的變量具有可見性,並且局部阻止了指令重排的發生。

可見性實現:當使用volatile修飾變量時,意味着任何對此變量的操作都會在內存中進行,不會產生副本,以保證共享變量的可見性。

禁止指令重排:CPU爲了提高程序指令的執行效率,會對輸入的代碼進行指令優化,分析哪些取數據動作可以合併進行和哪些存數據動作可以合併進行。它不保證各語句的執行順序與代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。指令重排不會影響單個線程的執行,但是會影響到多個線程併發執行的正確性。volatile會限制編譯器對修飾變量的相關讀寫操作和指令重排,確保在volatile之前的操作已經完成,在volatile之後的操作還未開始。

總結

  1. volatile僅能使用在變量上,synchronized可以使用在變量和方法上;
  2. volatile僅能實現變量的可見性,不能保證原子性,synchronized可以保證變量的可見性和原子性;
  3. volatile不會造成線程阻塞,synchronized可能會造成線程阻塞(因爲volatile只是將當前變量的值及時告知所有線程,而synchronized是鎖定當前變量不讓其它線程訪問);
  4. volatile標記的變量不會被編譯器優化(因爲不能指令重排),synchronized標記的變量可以被編譯器優化;
  5. volatile修飾變量適合於一寫多讀的併發場景,而多寫場景一定會產生線程安全問題(因此使用volatile而不是synchronized的唯一安全情況是類中只有一個可變的域)。
  6. 因爲所有的操作都需要同步給內存變量,所以volatile一定會使線程的執行速度變慢。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章