區別:1、volatile不會造成執行緒的阻塞;synchronized可能會造成執行緒的阻塞。 2.volatile保證資料的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性。
可見性(visibility)
可見性:一個執行緒對共享變數做了修改之後,其他的執行緒立即能夠看到(感知到)該變數這種修改(變化)。
Java記憶體模型是透過將在工作記憶體中的變數修改後的值同步到主內存,在讀取變數前從主記憶體刷新最新值到工作記憶體中,這種依賴主記憶體的方式來實現可見性的。
原子性(atomicity)
原子性:一個操作不能打斷,要嘛全部執行完畢,要嘛不執行。
java記憶體模型所保證的是,同執行緒內,所有的操作都是由上到下的,但是多個執行緒並行的情況下,則不能保證其操作的有序性。
有序性
有序性:在本執行緒內觀察,操作都是有順序的;如果在一個在線程中觀察另外一個線程,所有的操作都是無序的。
java記憶體模型所保證的是,同執行緒內,所有的操作都是由上到下的,但是多個執行緒並行的情況下,則不能保證其操作的有序性。
電腦在執行程式時,為了提升效能,編譯器個處理器常常會對指令做重排,一般分為以下3 種
單執行緒環境裡面確保程式最終執行的結果和程式碼執行的結果一致
處理器在進行重排序時必須考慮指令之間的資料依賴性
多執行緒環境中執行緒交替執行,由於編譯器最佳化重排的存在,兩個執行緒所使用的變數能否保證用的變數能否一致性是無法確定的,結果無法預測
考試先做會做的,不會做的後做。
public void mySort(){ int x = 11; //1 int y = 12; //2 x= x+5; // 3 y = x*x;//4
可能的順序1234 2134 1324,不可能的屬性4在1 和3前,因為有資料依賴性。
volatile禁止指令重排。
public class ReSortSeqDemo { int a = 0; boolean flag = false; public void method01() { a = 1; // flag = true; // ----线程切换---- flag = true; // a = 1; } public void method02() { if (flag) { a = a + 3; System.out.println("a = " + a); } } }
如果兩個執行緒同時執行,method01 和 method02 如果執行緒 1 執行 method01 重排序了,然後切換的執行緒 2 執行 method02 就會出現不一樣的結果。
禁止指令排序
volatile 實作禁止指令重排序的最佳化,從而避免了多執行緒環境下程式出現亂序的現象
先了解一個概念,記憶體屏障(Memory Barrier)又稱為記憶體柵欄,是一個CPU 指令,他的作用有兩個:
保證特定操作的執行順序
保證某一些變數的記憶體可見性(利用此特性實現volatile 的記憶體可見性)
由於編譯器個處理器都能執行指令重排序最佳化,如果在指令間插入一條Memory Barrier 則會告訴編譯器和CPU,不管什麼指令不能個這條Memory Barrier 指令重排序,也就是說透過插入記憶體屏障禁止在記憶體屏障前後執行重排序最佳化。記憶體屏障另一個作用是強制刷出各種 CPU 快取數據,因此任何 CPU 上的執行緒都能讀取到這些數據的最新版本。
下面是保守策略下,volatile寫插入記憶體屏障後產生的指令序列示意圖:
以下是在保守策略下,volatile讀取插入記憶體屏障後產生的指令序列示意圖:
線程安全性保證
工作記憶體與主記憶體同步延遲現象導致可見性問題
可以使用synchronzied 或volatile 關鍵字解決,它們可以使用一個執行緒修改後的變數立即對其他執行緒可見
對於指令重排導致可見性問題和有序性問題
可以利用volatile 關鍵字解決,因為volatile 的另一個作用就是禁止指令重新排序最佳化
volatile
##它所修飾的##變數不保留拷貝,直接存取主記憶體中的。 在Java記憶體模型中,有main memory,每個執行緒也有自己的memory (例如暫存器)。為了效能,一個執行緒會在自己的memory中保持要存取的變數的副本。這樣就會出現同一個變數在某個瞬間,在一個執行緒的memory中的值可能與另外一個執行緒memory中的值,或是main memory中的值不一致的情況。一個變數宣告為volatile,就表示這個變數是隨時會被其他執行緒修改的,因此不能將它cache在線程memory中。 使用場景 您只能在有限的一些情況下使用 volatile 變數取代鎖定。要讓 volatile 變數提供理想的執行緒安全,必須同時滿足下面兩個條件: volatile最適用一個執行緒寫,多個執行緒讀的場合。 synchronized 當它用來修飾一個方法或一個程式碼區塊的時候,能夠保證在同一時刻最多只有一個執行緒執行該段程式碼。 Lock 從jdk 5.0開始,java提供了更強大的執行緒同步機制-透過顯示定義同步鎖定物件來實現同步,同步鎖定使用Lock物件充當。 java.util.concurrent.Locks.Lock介面是控制多個執行緒對共享資源進行存取的工具。鎖提供了對共享資源的獨佔訪問,每次只能有一個線程對Lock物件加鎖,線程開始訪問共享資源之前應先獲得Lock物件。 ReentrantLock類別實作了Lock,它擁有與synchronized相同的並發性和記憶體語義,在實作執行緒安全的控制中,比較常用的是ReentrantLock,可以顯示加鎖、釋放鎖。 區別 #volatile和synchronized #volatile是變數修飾符,而synchronized則作用於一段程式碼或方法。 volatile只是在線程記憶體和「主」記憶體間同步某個變數的值;而synchronized透過鎖定和解鎖某個監視器同步所有變數的值, 顯然synchronized要比volatile消耗更多資源。 volatile不會造成執行緒的阻塞;synchronized可能會造成執行緒的阻塞。 volatile保證資料的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有記憶體中和公用記憶體中的資料做同步。 volatile標記的變數不會被編譯器最佳化;synchronized標記的變數可以被編譯器最佳化。 線程安全性包含原子性和可見性兩個方面,Java的同步機制都是圍繞著這兩個方面來確保線程安全的。 關鍵字volatile提示線程每次從共享記憶體中讀取變量,而不是私有記憶體中讀取,這樣就保證了同步資料的可見性。但是要注意的是:如果修改實例變數中的資料 例如:i ,也就是i=i 1,則這樣的操作其實並不是原子操作,也就是非執行緒安全的。表達式i 操作步驟分解如下: synchronized 和Lock Lock是顯示鎖(手動開啟和關閉,別忘記關閉鎖),synchronized是隱式鎖,出了作用域自動釋放鎖。 更多程式設計相關知識,請造訪:程式設計學習課程! !
1)對變數的寫入操作不依賴目前值。
2)該變數沒有包含在具有其他變數的不變式中。
如果有多個執行緒並發寫入操作,仍然需要使用鎖定或執行緒安全的容器或原子變數來取代。
共享資源及增刪改的物件。
關鍵字volatile主要使用的場合是在多個執行緒中可以感知實例變數被修改,並且可以獲得最新的值使用,也就是多執行緒讀取共享變數時可以獲得最新值使用。
1)從記憶體中取出i的值。
2)計算i的值;
3)將i的值寫到記憶體中。
假如在步驟2計算值得時候,另外一個執行緒也修改i的值,name這個時候就會出現髒讀資料。解決的方法就是使用synchronized關鍵字。 所以說volatile本身並沒有處理資料的原子性,而是強制對資料的讀寫及時的影響到主記憶體。
Lock只有程式碼區塊鎖,synchronized可以作用程式碼區塊和方法。
使用Lock鎖,jvm花費較少的時間來調度線程,效能更好。並且具有更好的擴展性(提供更多的子類別)。
使用順序:Lock->同步程式碼區塊(已經進入了方法體,分配了對應資源)->同步方法(在方法體之外)。
以上是volatile和synchronize的差別是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!