1. 程式人生 > >Java多執行緒--重排序與順序一致性

Java多執行緒--重排序與順序一致性

前言

在我們編寫程式並執行的時候,編譯器給我們一個錯覺:程式編譯的順序與編寫的順序是一致的。但是實際上,為了提高效能,編譯器和處理器常常會對指令進行重排序。重排序主要分為兩類:編譯器優化的重排序、指令級別並行的重排序和記憶體系統的重排序。所以我們編寫好Java原始碼之後,會經過以上三個重排序,到最終的指令序列。我們這裡提到的Java記憶體模型又是什麼呢?Java記憶體模型(後面簡稱JMM)是語言級別的記憶體模型,主要用於控制一個共享變數的寫入何時對另一個執行緒可見(後面所有方面都是圍繞這點展開的)。JMM可以確保在不同的處理器平臺和編譯器之上,通過禁止特定型別的編譯器重排序和處理器重排序,為程式設計師提供一致的記憶體可見性的保證。

JMM對重排序的處理

對於編譯器的重排序,JMM會根據重排序規則禁止特定型別的編譯器重排序;對於處理器重排序,JMM會插入特定型別的記憶體屏障,通過記憶體的屏障指令禁止特定型別的處理器重排序。這裡討論JMM對處理器的重排序,為了更深理解JMM對處理器重排序的處理,先來認識一下常見處理器的重排序規則:

處理器\規則 Load-Load Load-Store Store-Store Store-Load 資料轉換
SPARC-TSO N N N Y N
x86 N N N Y N
IA64 Y Y Y Y N
SPARC-TSO Y Y Y Y N

其中的N標識處理器不允許兩個操作進行重排序,Y表示允許。其中Load-Load表示讀-讀操作、Load-Store表示讀-寫操作、Store-Store表示寫-寫操作、Store-Load表示寫-讀操作。可以看出:常見處理器對寫-讀操作都是允許重排序的,並且常見的處理器都不允許對存在資料依賴的操作進行重排序(對應上面資料轉換那一列,都是N,所以處理器不允許這種重排序)。

那麼這個結論對我們有什麼作用呢?比如第一點:處理器允許寫-讀操作兩者之間的重排序,那麼在併發程式設計中讀執行緒讀到可能是一個未被初始化或者是一個NULL等,出現不可預知的錯誤,基於這點,JMM會在適當的位置插入記憶體屏障指令來禁止特定型別的處理器的重排序。記憶體屏障指令一共有4類:

  1. LoadLoad Barriers:確保Load1資料的裝載先於Load2以及所有後續裝載指令
  2. StoreStore Barriers:確保Store1的資料對其他處理器可見(會使快取行無效,並重新整理到記憶體中)先於Store2及所有後續儲存指令的裝載
  3. LoadStore Barriers:確保Load1資料裝載先於Store2及所有後續儲存指令重新整理到記憶體
  4. StoreLoad Barriers:確保Store1資料對其他處理器可見(重新整理到記憶體,並且其他處理器的快取行無效)先於Load2及所有後續裝載指令的裝載。該指令會使得該屏障之前的所有記憶體訪問指令完成之後,才能執行該屏障之後的記憶體訪問指令。

重排序

重排序指的是編譯器和處理器為了優化程式效能而對指令序列進行重新排序的手段。根據上面的表格,得到了處理器不會對存在資料依賴的操作進行重排序。這裡資料依賴的準確定義是:如果兩個操作同時訪問一個變數,其中一個操作是寫操作,此時這兩個操作就構成了資料依賴。常見的具有這個特性的如i++、i–。如果改變了具有資料依賴的兩個操作的執行順序,那麼最後的執行結果就會被改變。這也是不能進行重排序的原因。

as-if-serial語義

是不是有點突兀,怎麼突然蹦出這個玩意。還是先解釋這個語義的含義吧:不管怎麼重排序,單執行緒程式的執行結果不能發生改變。編譯器、Runtime和處理器也是如此。這個語義相當於把單執行緒保護起來了,所以即使編譯器和處理器對指令序列進行了重排序,我們也會認為程式指令並沒有發生重排序,也就出現了篇首的幻覺。

重排序對多執行緒的影響

如果程式碼中存在控制依賴的時候,會影響指令序列執行的並行度(因為高效)。也是為此,編譯器和處理器會採用猜測(Speculation)執行來克服控制的相關性。所以重排序破壞了程式順序規則(該規則是說指令執行順序與實際程式碼的執行順序是一致的,但是處理器和編譯器會進行重排序,只要最後的結果不會改變,該重排序就是合理的)。

重排序的小結

在單執行緒程式中,對存在依賴關係的操作進行重排序,不會改變最後的執行結果;在多執行緒程式中,對存在依賴關係的操作進行重排序,可能會改變最後的執行結果。

順序一致性

順序一致性實際上指的是一個記憶體模型,JMM對順序一致模型進行了更嚴格的規定。所以JMM是以順序一致性模型進行參照的。

資料競爭 
如果程式沒有正確同步,那麼可能會存在資料競爭。JMM對資料競爭的定義如下:

  1. 在一個執行緒中寫一個變數
  2. 在另一個執行緒中讀取同一個變數
  3. 而且寫和讀沒有通過同步來排序

    那麼,反過來說,如果一個多執行緒程式已經正確同步,這個程式將是一個沒有資料競爭的程式。JMM是如何保證這點的呢?

如果程式是正確同步的,程式的執行將有順序一致性——也就是說程式的執行結果與該程式在順序一致記憶體模型中的執行結果是一致的。

順序一致性模型

順序一致性模型有以下兩大特性:

  1. 一個執行緒中的所有操作必須按照程式的順序來執行
  2. (不管程式是否同步)所有執行緒都只能看到一個單一的操作執行順序。在順序一致記憶體模型中,每一個操作都必須是院子執行且立即對所有執行緒可見。

可以把順序順序一致模型理解為一個單擺,每一個時刻單擺只能到一個位置,對應過來,任何時刻最多隻能有一個執行緒才能連線到記憶體。由於重排序的影響,實際指令的執行順序是不可知的,但是不管如何排序,每個操作能夠立即對其他執行緒可見,所以所有執行緒看到的都是一樣的執行順序。但是在JMM中是沒有這個規定的,就是說其他執行緒看到執行順序與除自己外的執行緒看到的執行順序可能是不一致的。比如,當前執行緒把寫過的資料快取快取到寫快取中,在沒有重新整理到主記憶體(計算機系統的DRAM)之前,這個寫操作對其他執行緒是不可見的,意味著其他執行緒認為該執行緒根本沒有執行寫操作。那麼何時才能可見呢?只有在當前執行緒把寫快取中資料重新整理到主記憶體的時候,對其他記憶體才是可見的。

原文連結 http://blog.csdn.net/u011116672/article/details/50130367

相關推薦

Java執行--排序順序一致性

前言 在我們編寫程式並執行的時候,編譯器給我們一個錯覺:程式編譯的順序與編寫的順序是一致的。但是實際上,為了提高效能,編譯器和處理器常常會對指令進行重排序。重排序主要分為兩類:編譯器優化的重排序、指令級別並行的重排序和記憶體系統的重排序。所以我們編寫好Java原始碼之後

java執行8.效能活躍性問題

死鎖——鎖順序死鎖 兩個執行緒試圖以不同的順序來獲得相同的鎖。如果按照相同的順序來請求鎖,那麼就不會出現迴圈的加鎖依賴,因此也就不會產生死鎖。 public class LeftRightDeadlock { private final Object left = new Object();

Java執行下載原理實現

多執行緒下載原理 客戶端要下載一個檔案, 首先請求伺服器,伺服器將這個檔案傳送給客戶端,客戶端儲存到本地, 完成了一個下載的過程. 多執行緒下載的思想是客戶端開啟多個執行緒同時下載,每個執行緒只負責下載檔案的一部分, 當所有執行緒下載完成的時候,檔案下載完畢.

Java執行——鎖概念鎖優化

為了效能與使用的場景,Java實現鎖的方式有非常多。而關於鎖主要的實現包含synchronized關鍵字、AQS框架下的鎖,其中的實現都離不開以下的策略。 悲觀鎖與樂觀鎖 樂觀鎖。樂觀的想法,認為併發讀多寫少。每次操作的時候都不上鎖,直到更新的時候才通過CAS判斷更新。對於AQS框架下的鎖,初始就是

JAVA執行 入鎖和讀寫鎖

在java多執行緒中,我們真的可以使用synchronized關鍵字來實現執行緒間的同步互斥工作,那麼其實還有一個更優秀的機制去完成這個“同步互斥”工作,他就是Lock物件,重入鎖和讀寫鎖。他們具有比synchronized更為強大的功能,並且有嗅探鎖定、多路分支等功能。 一、重入鎖

java執行容器ConcurrentMapCopyOnWrite

一、ConcurrentMap介面下有兩個重要的實現: ConCurrentHashMap ConcurrentSkipListMap(支援併發排序功能,彌補ConcurrentHashMap) ConcurrentHashMap內部使用段(Segment)來表示這些不同的部分,每個段其

JAVA執行之volatile synchronized 的比較

一,volatile關鍵字的可見性 要想理解volatile關鍵字,得先了解下JAVA的記憶體模型,Java記憶體模型的抽象示意圖如下: 從圖中可以看出: ①每個執行緒都有一個自己的本地記憶體空間--執行緒棧空間???執行緒執行時,先把變數從主記憶體讀取到執行緒自己

Java 執行的生產銷售應用

 生產與消費多執行緒程式, 編寫電影院生產10個電影,一邊生產(播放)一邊消費(觀看)。  如圖效果: 共享資源:電影 class Movie { private String name; // 訊號燈 // flag=true 生

java執行之synchronizedlock、waitnotify

class Res { public String name; public String sex; public Boolean flag = false; public Lock lock = new ReentrantLock(); Condition condition = lock.new

js單執行java執行、同步非同步

       寫這篇部落格源於想對比一下單執行緒js和多執行緒java兩種語言的區別。       定義區:            單執行緒:只能執行一個任務,只有在完成執行後,才能繼續執行其他的任務。            多執行緒:有多個執行緒,可以同時執行多個任務。

Java執行--入鎖的實現原理

protectedfinalboolean tryRelease(int releases) {               int c = getState() - releases;               if (Thread.currentThread() != getExclusiveO

Java執行之狀態生命週期

執行緒的生命週期 執行緒建立並啟動後,不是一啟動就進入執行狀態,也不會一直處於執行狀態。 執行緒啟動後不可能一直霸佔CPU,所以CPU會在多執行緒之間切換,於是執行緒狀態也會多次在執行、阻塞之間切換 線上程的生命週期中,執行緒共有5種狀態,在任意時刻,執行

Java執行】synchronized執行安全

介紹 修飾方法:一個執行緒去呼叫一個加synchronized的方法的時候,會獲得該物件的 物件鎖。 修飾靜態方法:一個執行緒去呼叫一個既加static,又加synchronized的方法的時候,會獲得該物件的 類鎖。 修飾程式碼塊: ①加物件鎖:

Java執行1:程序執行

1.什麼是程序? 程序是作業系統結構的基礎,是一次程式的執行,是系統進行資源分配和排程的一個獨立單位。 這個解釋有點懵了。簡單來講就是一個正在作業系統中的執行的exe程式就是一個程序。 2.什麼是執行緒? 執行緒可以理解為是在程序中獨立執行的子任務。

Java 執行全域性鎖物件鎖

  我們看一個例子: class Demo { public synchronized void test() { System.out.println("test方法開始執行,當前執行緒為:"+Thread.currentThre

Java執行Runnable介面Thread類,以及synchronied關鍵字

Java多執行緒實現有兩種方式一個是實現Runnable介面一個是繼承Thread類 如果只是為了實現Thread的執行過程,那麼沒有必要從Thread中派生,因為是是實現Runnable介面的物件代表的是一個計算任務,Thread類對應任務的執行者。 如果執行緒類繼承Th

Java執行程式設計學習實踐

怎麼樣才算得上熟悉多執行緒程式設計?第一,明白程序和執行緒的基本概念第二,明白保護執行緒安全的基本方法有哪些第三,明白這些執行緒安全的方法,包括互斥鎖,自旋鎖,無鎖程式設計的適用的業務場景是什麼?從OS和硬體角度說說原理是怎麼樣的?開銷在哪裡?第四,能在現場藉助cas操作,風

java執行----悲觀鎖樂觀鎖

java多執行緒中悲觀鎖與樂觀鎖思想   一、悲觀鎖 總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖(共享資源每次只給一個執行緒使用,其它執行緒阻塞,用完後再把資源轉讓給其它執行緒)。傳統的關係型資料庫裡邊就用到了很

JAVA執行使用synchronizedwait,notify實現排它,同步通訊

JAVA使用多執行緒實現上傳圖片,當上傳結束後,再通過另外一個執行緒告知下載圖片結束,這時候就需要用到synchronized以及

Java執行鎖模型-順序資源鎖

順序鎖:當應用程式使用2把以上的鎖時,就容易出現因為多執行緒獲取鎖的順序不同而死鎖的情形,包括交叉獲取應用程式範圍內的多把已知鎖、交叉獲取應用程式與第三方方法中的多把鎖而造成的順序死鎖。絕大多數死鎖都是因為CPU排程多執行緒時,在執行時序上是交叉進行的而造成亂序獲得多把鎖,從