Java內存模型FAQ(一) 什麼是內存模型

杜老師說 2022-01-07 17:27:22 阅读数:712

java 模型 faq 模型

原文:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 第一章 譯者:方騰飛

在多核系統中,處理器一般有一層或者多層的緩存,這些的緩存通過加速數據訪問(因為數據距離處理器更近)和降低共享內存在總線上的通訊(因為本地緩存能够滿足許多內存操作)來提高CPU性能。緩存能够大大提昇性能,但是它們也帶來了許多挑戰。例如,當兩個CPU同時檢查相同的內存地址時會發生什麼?在什麼樣的條件下它們會看到相同的值?

在處理器層面上,內存模型定義了一個充要條件,“讓當前的處理器可以看到其他處理器寫入到內存的數據”以及“其他處理器可以看到當前處理器寫入到內存的數據”。有些處理器有很强的內存模型(strong memory model),能够讓所有的處理器在任何時候任何指定的內存地址上都可以看到完全相同的值。而另外一些處理器則有較弱的內存模型(weaker memory model),在這種處理器中,必須使用內存屏障(一種特殊的指令)來刷新本地處理器緩存並使本地處理器緩存無效,目的是為了讓當前處理器能够看到其他處理器的寫操作或者讓其他處理器能看到當前處理器的寫操作。這些內存屏障通常在lock和unlock操作的時候完成。內存屏障在高級語言中對程序員是不可見的。

在强內存模型下,有時候編寫程序可能會更容易,因為减少了對內存屏障的依賴。但是即使在一些最强的內存模型下,內存屏障仍然是必須的。設置內存屏障往往與我們的直覺並不一致。近來處理器設計的趨勢更傾向於弱的內存模型,因為弱內存模型削弱了緩存一致性,所以在多處理器平臺和更大容量的內存下可以實現更好的可伸縮性

“一個線程的寫操作對其他線程可見”這個問題是因為編譯器對代碼進行重排序導致的。例如,只要代碼移動不會改變程序的語義,當編譯器認為程序中移動一個寫操作到後面會更有效的時候,編譯器就會對代碼進行移動。如果編譯器推遲執行一個操作,其他線程可能在這個操作執行完之前都不會看到該操作的結果,這反映了緩存的影響。

此外,寫入內存的操作能够被移動到程序裏更前的時候。在這種情况下,其他的線程在程序中可能看到一個比它實際發生更早的寫操作。所有的這些靈活性的設計是為了通過給編譯器,運行時或硬件靈活性使其能在最佳順序的情况下來執行操作。在內存模型的限定之內,我們能够獲取到更高的性能。

看下面代碼展示的一個簡單例子:

ClassReordering {int x = 0, y = 0;public void writer() {x = 1;y = 2;}public void reader() {int r1 = y;int r2 = x;}}

讓我們看在兩個並發線程中執行這段代碼,讀取Y變量將會得到2這個值。因為這個寫入比寫到X變量更晚一些,程序員可能認為讀取X變量將肯定會得到1。但是,寫入操作可能被重排序過。如果重排序發生了,那麼,就能發生對Y變量的寫入操作,讀取兩個變量的操作緊隨其後,而且寫入到X這個操作能發生。程序的結果可能是r1變量的值是2,但是r2變量的值為0。

Java內存模型描述了在多線程代碼中哪些行為是合法的,以及線程如何通過內存進行交互。它描述了“程序中的變量“ 和 ”從內存或者寄存器獲取或存儲它們的底層細節”之間的關系。Java內存模型通過使用各種各樣的硬件和編譯器的優化來正確實現以上事情。

Java包含了幾個語言級別的關鍵字,包括:volatile, final以及synchronized,目的是為了幫助程序員向編譯器描述一個程序的並發需求。Java內存模型定義了volatile和synchronized的行為,更重要的是保證了同步的java程序在所有的處理器架構下面都能正確的運行。


原文

What is a memory model, anyway?

In multiprocessor systems, processors generally have one or more layers of memory cache, which improves performance both by speeding access to data (because the data is closer to the processor) and reducing traffic on the shared memory bus (because many memory operations can be satisfied by local caches.) Memory caches can improve performance tremendously, but they present a host of new challenges. What, for example, happens when two processors examine the same memory location at the same time? Under what conditions will they see the same value?

At the processor level, a memory model defines necessary and sufficient conditions for knowing that writes to memory by other processors are visible to the current processor, and writes by the current processor are visible to other processors. Some processors exhibit a strong memory model, where all processors see exactly the same value for any given memory location at all times. Other processors exhibit a weaker memory model, where special instructions, called memory barriers, are required to flush or invalidate the local processor cache in order to see writes made by other processors or make writes by this processor visible to others. These memory barriers are usually performed when lock and unlock actions are taken; they are invisible to programmers in a high level language.

It can sometimes be easier to write programs for strong memory models, because of the reduced need for memory barriers. However, even on some of the strongest memory models, memory barriers are often necessary; quite frequently their placement is counterintuitive. Recent trends in processor design have encouraged weaker memory models, because the relaxations they make for cache consistency allow for greater scalability across multiple processors and larger amounts of memory.

The issue of when a write becomes visible to another thread is compounded by the compiler’s reordering of code. For example, the compiler might decide that it is more efficient to move a write operation later in the program; as long as this code motion does not change the program’s semantics, it is free to do so.  If a compiler defers an operation, another thread will not see it until it is performed; this mirrors the effect of caching.

Moreover, writes to memory can be moved earlier in a program; in this case, other threads might see a write before it actually “occurs” in the program.  All of this flexibility is by design — by giving the compiler, runtime, or hardware the flexibility to execute operations in the optimal order, within the bounds of the memory model, we can achieve higher performance.

A simple example of this can be seen in the following code:

Class Reordering { int x = 0, y = 0; public void writer() { x = 1; y = 2; } public void reader() { int r1 = y; int r2 = x; }}

Let’s say that this code is executed in two threads concurrently, and the read of y sees the value 2. Because this write came after the write to x, the programmer might assume that the read of x must see the value 1. However, the writes may have been reordered. If this takes place, then the write to y could happen, the reads of both variables could follow, and then the write to x could take place. The result would be that r1 has the value 2, but r2 has the value 0.

The Java Memory Model describes what behaviors are legal in multithreaded code, and how threads may interact through memory. It describes the relationship between variables in a program and the low-level details of storing and retrieving them to and from memory or registers in a real computer system. It does this in a way that can be implemented correctly using a wide variety of hardware and a wide variety of compiler optimizations.

Java includes several language constructs, including volatile, final, and synchronized, which are intended to help the programmer describe a program’s concurrency requirements to the compiler. The Java Memory Model defines the behavior of volatile and synchronized, and, more importantly, ensures that a correctly synchronized Java program runs correctly on all processor architectures.

原創文章,轉載請注明: 轉載自並發編程網 – ifeve.com本文鏈接地址: Java內存模型FAQ(一) 什麼是內存模型

FavoriteLoading添加本文到我的收藏
版权声明:本文为[杜老師說]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201071727222734.html