2017-05-06 154 views
1

讓我們看看下面這段代碼在Java中揮發性VS不揮發

int x = 0; 
int who = 1 
Thread #1: 
    (1) x++; 
    (2) who = 2; 

Thread #2 
    while(who == 1); 
    x++; 
    print x; (the value should be equal to 2 but, perhaps, it is not*)  

(我不知道Java的內存模型 - 讓假定它是強大的內存模型 - 我的意思是:(1)和( 2)將不會交換)
Java內存模型保證訪問/存儲到32位變量是原子的,所以我們的程序是安全的。但是,我們應該使用屬性volatile,因爲*。 x的值可能等於1,因爲x可以在Thread#2讀取時保存在寄存器中。要解決它,我們應該使x變量volatile。很明顯。

但是,關於這種情況是什麼:

int x = 0; 
    mutex m; (just any mutex) 
Thread #1: 
     mutex.lock() 
     x++; 
     mutex.unlock() 

    Thread #2 
     mutex.lock() 
     x++; 
     print x; // the value is always 2, why**? 
     mutex.unlock() 

x值總是2雖然我們不讓它volatile。我是否正確理解,鎖定/解鎖互斥體是通過插入內存屏障來連接的?

+0

請發佈一個MCVE。 –

+0

什麼是MCVE? – Gilgamesz

+1

閱讀關於它[這裏](https://stackoverflow.com/help/mcve) –

回答

3

我會盡力解決這個問題。 Java內存模型是一種涉及並很難包含在一個單一的StackOverflow後。請參閱Brian Goetz的Java併發實踐瞭解全部內容。

x的值始終爲2,儘管我們不會使其變得不穩定。我是否正確理解,鎖定/解鎖互斥體是通過插入內存屏障來連接的?

首先,如果您想了解Java內存模型,您總是需要通讀Chapter 17 of the spec

即規範說:

解鎖在顯示器的之前發生在該顯示器上每一個後續的鎖。

所以是的,在顯示器解鎖時會出現內存可見性事件。 (我所說的「互斥」假設你的意思監測,大部分在java.utils.concurrent包鎖和其他類的也有之前發生語義,查看文檔)。

之前發生就是Java的意思時它不僅保證事件是有序的,而且保證內存可見性。

We say that a read r of a variable v is allowed to observe a write w 
to v if, in the happens-before partial order of the execution trace: 

    r is not ordered before w (i.e., it is not the case that 
    hb(r, w)), and 

    there is no intervening write w' to v (i.e. no write w' to v such 
    that hb(w, w') and hb(w', r)). 

Informally, a read r is allowed to see the result of a write w if there 
is no happens-before ordering to prevent that read. 

這全是來自17.4.5。通讀它有點令人困惑,但如果你仔細閱讀,信息就會全部存在。

+0

@scottb那麼他似乎在問關於互斥/監視器。 「易變」只是他前提的一部分。當然,我們經常可以在這裏回答具體的問題,但理解完整的內存模型對於在Java中良好編程是非常必要的。例如,很多人都知道'volatile'具有什麼作用,但很少有人知道'final'的作用。 (提示:閱讀規範的第17章!!) – markspace

1

讓我們來看看一些事情。以下語句是正確的:Java內存模型保證訪問/存儲到32位變量是原子的。但是,這並不表示您列出的第一個僞程序是安全的。僅僅因爲兩條語句按照語法順序排​​列,而不是意味着它們的更新的可見性也按其他線程查看的順序排列。在x中的增量可見之前,線程#2可能會看到由who = 2引起的更新。使x變爲volatile將仍然不能使程序正確。相反,使變量'誰'voliatile將使程序正確。這是因爲volatile以特定的方式與java內存模型進行交互。

我覺得有一些'寫回主存'的概念是關於volatile的常識理解的核心,這是不正確的。易失性不會將值寫回到Java中的主內存。從volatile變量中讀取和寫入的內容就是創建一個叫做'before-before'的關係。當線程#1寫入一個volatile變量時,您正在創建一個關係,以確保查看該volatile變量的任何其他線程#2也能夠「查看」線程#1在此之前採取的所有操作。在你的例子中,這意味着讓'誰'變得不穩定。通過將值2寫入「誰」,您正在創建一個「先發生」關係,以便當線程#2查看who = 2時,它將同樣看到x的更新版本。

在你的第二個例子中(假設你也打算使用'who'變量),互斥鎖解鎖會創建一個before-before關係,如上所述。因爲這意味着其他線程查看互斥鎖的解鎖(即他們能夠自己鎖定),他們將看到x的更新版本。

+1

「由其他線程查看」在這裏是關鍵。 *線程中的語句*保證按程序順序執行(它們在代碼中出現的順序)。內存寫入的可見性*由其他線程看到*根本無法保證。需要互斥或'volatile'來完成關於線程2在「可能」之外看到的任何聲明。 – markspace

+0

也是「寫回主內存的概念」:是的。 Java是爲硬件編寫的,它不一定是緩存一致的。 Brian Goetz在* Java Concurrency in Practice *中談論了Java設計的這一方面,並​​說Java是爲這種CPU設計的。這有點奇怪,但很容易習慣。然而,'volatile'明確地*會創建一個「flush到主內存」,這就是發生的事情 - 之前的意思(在其他一些事情中)。 – markspace

+0

從功能上來說,我不確定'刷到主內存'是正確的方式來說明發生了什麼。我的理解是,易失性通常是作爲內存屏障實施的。這些結構不一定會導致CPU緩存刷新;他們只是限制瞭如何發生內存重新排序,這會影響處理器如何看待數據。 –