JSR-133 FAQ 中英對(duì)照版翻譯

由于本人能力有限,如有錯(cuò)誤,歡迎指出。
原文地址https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
如果你喜歡原文那種板式的話,可以看這個(gè)https://yellowstar5.cn/direct/jsr-133-faq-chinese.html

What is a memory model, anyway? (無論如何,什么是內(nèi)存模型?)

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?

在多處理器系統(tǒng)中,處理器通常具有一層或多層內(nèi)存高速緩存, 這可以通過加快對(duì)數(shù)據(jù)的訪問速度 (因?yàn)閿?shù)據(jù)更靠近處理器) 和減少共享內(nèi)存總線上的通信量 (因?yàn)楸镜鼐彺婵梢詽M足許多內(nèi)存操作。)來提高性能。內(nèi)存緩存可以極大地提高性能,但是它們帶來了許多新的挑戰(zhàn)。 例如,當(dāng)兩個(gè)處理器同時(shí)檢查相同的內(nèi)存位置時(shí)會(huì)發(fā)生什么? 他們將在什么條件下看到相同的價(jià)值?

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.

在處理器級(jí)別,內(nèi)存模型定義了必要條件和充分條件,以便知道其他處理器對(duì)內(nèi)存的寫操作對(duì)當(dāng)前處理器可見,和當(dāng)前處理器的寫操作對(duì)其他處理器可見。 一些處理器表現(xiàn)出強(qiáng)大的內(nèi)存模型,其中所有處理器始終在任何給定的內(nèi)存位置看到完全相同的值。 其他處理器表現(xiàn)出較弱的內(nèi)存模型,其中需要特殊的指令(稱為內(nèi)存屏障)來刷新或使本地處理器緩存無效, 以便該本地處理器看到其他處理器做出的寫入或使該處理器的寫入對(duì)其他處理器可見。 這些內(nèi)存屏障通常在執(zhí)行鎖定和解鎖操作時(shí)執(zhí)行; 使用高級(jí)語言的程序員看不到它們。

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.

有時(shí)為強(qiáng)大的內(nèi)存模型編寫程序可能會(huì)更容易,因?yàn)闇p少了對(duì)內(nèi)存屏障的需求。 但是,即使在某些最強(qiáng)大的內(nèi)存模型上,也經(jīng)常需要使用內(nèi)存屏障。 它們的放置經(jīng)常違反直覺。 處理器設(shè)計(jì)的最新趨勢(shì)鼓勵(lì)使用較弱的內(nèi)存模型,因?yàn)樗鼈儗?duì)高速緩存一致性的放寬允許跨多個(gè)處理器的更大可伸縮性和更大的內(nèi)存量。

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.

關(guān)于何時(shí)一個(gè)寫操作對(duì)另一個(gè)線程可見的問題被編譯器對(duì)代碼的重新序復(fù)雜化了。 例如,編譯器可能認(rèn)為把寫操作移到程序的后面會(huì)更有效;只要這個(gè)代碼移動(dòng)不改變程序的語義,編譯器可以自由地這樣做。 如果一個(gè)編譯器延遲了一個(gè)操作,另一個(gè)線程將看不到它,直到它被執(zhí)行;這反映了緩存的效果

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.

此外,寫入內(nèi)存的操作可以在程序中被提前移動(dòng); 在這種情況下,其他線程可能會(huì)在該操作在程序中實(shí)際“發(fā)生”之前看到該操作。 所有這些靈活性都是設(shè)計(jì)出來的 —— 通過在內(nèi)存模型的范圍內(nèi)給編譯器、運(yùn)行時(shí)或硬件靈活性以最佳順序執(zhí)行操作,我們可以實(shí)現(xiàn)更高的性能。

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

一個(gè)簡(jiǎn)單的例子可以在下面的代碼中看到:

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.

假設(shè)此代碼是在兩個(gè)線程中同時(shí)執(zhí)行的,而 y 的讀取將看到值 2 。 由于此寫入是在寫入 x 之后完成的,因此程序員可能會(huì)認(rèn)為 x 的讀取必須看到值 1 。但是,寫入可能已被重排序。 如果發(fā)生這種情況,則可能發(fā)生對(duì) y 的寫入,隨后是兩個(gè)變量的讀取,然后可能發(fā)生對(duì)x的寫入。 結(jié)果將是r1的值為2,而r2的值為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 內(nèi)存模型描述了多線程代碼中哪些行為是合法的,以及線程如何通過內(nèi)存進(jìn)行交互。 它描述了程序中的變量與在真實(shí)計(jì)算機(jī)系統(tǒng)中的存儲(chǔ)器或寄存器進(jìn)行存儲(chǔ)和獲取變量的底層細(xì)節(jié)之間的關(guān)系。 它以這樣一種方式來實(shí)現(xiàn)上面要求,該方式使用各種硬件和各種編譯器優(yōu)化來正確實(shí)現(xiàn)。

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.

Java 包括幾種語言結(jié)構(gòu),包括 volatile,final 和 synchronized, 旨在幫助程序員向編譯器描述程序的并發(fā)要求。 Java 內(nèi)存模型定義了 volatile 和 synchronized 的行為, 并且更重要的是,確保正確同步的 Java 程序可以在所有處理器體系結(jié)構(gòu)上正確運(yùn)行。

Do other languages, like C++, have a memory model? (其他語言(例如 C++)是否具有內(nèi)存模型?)

Most other programming languages, such as C and C++, were not designed with direct support for multithreading. The protections that these languages offer against the kinds of reorderings that take place in compilers and architectures are heavily dependent on the guarantees provided by the threading libraries used (such as pthreads), the compiler used, and the platform on which the code is run.

大多數(shù)其他編程語言,比如 C 和 C++,在設(shè)計(jì)時(shí)并沒有直接支持多線程。 這些語言對(duì)發(fā)生在編譯器和體系結(jié)構(gòu)中的各種重排序所提供的保護(hù)在很大程度上依賴于所使用的線程庫(例如 pthreads )、 所使用的編譯器和運(yùn)行代碼的平臺(tái)所提供的保證

What is JSR 133 about? (JSR 133是關(guān)于什么的?)

Since 1997, several serious flaws have been discovered in the Java Memory Model as defined in Chapter 17 of the Java Language Specification. These flaws allowed for confusing behaviors (such as final fields being observed to change their value) and undermined the compiler's ability to perform common optimizations.

自 1997 年以來,在 Java 語言規(guī)范第 17 章定義的 Java 內(nèi)存模型中發(fā)現(xiàn)了幾個(gè)嚴(yán)重的缺陷。這些缺陷導(dǎo)致了令人困惑的行為(比如 final 字段被觀察到更改了它們的值), 并且破壞了編譯器執(zhí)行常見優(yōu)化的能力。

The Java Memory Model was an ambitious undertaking; it was the first time that a programming language specification attempted to incorporate a memory model which could provide consistent semantics for concurrency across a variety of architectures. Unfortunately, defining a memory model which is both consistent and intuitive proved far more difficult than expected. JSR 133 defines a new memory model for the Java language which fixes the flaws of the earlier memory model. In order to do this, the semantics of final and volatile needed to change.

Java 內(nèi)存模型是一個(gè)雄心勃勃的事業(yè)。 這是編程語言規(guī)范首次嘗試納入一種內(nèi)存模型,該模型可以為各種體系結(jié)構(gòu)中的并發(fā)提供一致的語義。 不幸的是,事實(shí)證明,定義一個(gè)既一致又直觀的內(nèi)存模型比預(yù)期的要困難得多。 JSR 133 為 Java 語言定義了一種新的內(nèi)存模型,該模型修復(fù)了早期內(nèi)存模型的缺陷。 為此,需要更改 final 和 volatile 的語義。

The full semantics are available at http://www.cs.umd.edu/users/pugh/java/memoryModel, but the formal semantics are not for the timid. It is surprising, and sobering, to discover how complicated seemingly simple concepts like synchronization really are. Fortunately, you need not understand the details of the formal semantics -- the goal of JSR 133 was to create a set of formal semantics that provides an intuitive framework for how volatile, synchronized, and final work.

完整的語義可以在 http://www.cs.umd.edu/users/pugh/java/memoryModel 可獲得,但是形式上的語義并不適合膽小者。 發(fā)現(xiàn)同步之類的看似簡(jiǎn)單的概念到底有多復(fù)雜,這是令人驚訝且發(fā)人深省的。 幸運(yùn)的是,你不需要了解形式語義的詳細(xì)信息 —— JSR 133 的目標(biāo)是創(chuàng)建一組形式語義,以提供直觀的框架來說明 volatile,synchronized 和 final 是如何工作的。

The goals of JSR 133 include:

JSR 133的目標(biāo)包括:

  • Preserving existing safety guarantees, like type-safety, and strengthening others. For example, variable values may not be created "out of thin air": each value for a variable observed by some thread must be a value that can reasonably be placed there by some thread.
    保留現(xiàn)有的安全保證,例如類型安全,并加強(qiáng)其他安全保證。 例如,可能不會(huì)“憑空”創(chuàng)建變量值:某個(gè)線程觀察到的變量的每個(gè)值必須是某個(gè)線程可以合理放置在其中的值。

  • The semantics of correctly synchronized programs should be as simple and intuitive as possible.
    正確同步的程序的語義應(yīng)盡可能簡(jiǎn)單直觀。

  • The semantics of incompletely or incorrectly synchronized programs should be defined so that potential security hazards are minimized.
    不完整或不正確同步的程序的語義應(yīng)該被定義,以使?jié)撛诘陌踩[患最小化。

  • Programmers should be able to reason confidently about how multithreaded programs interact with memory.
    程序員應(yīng)該能夠自信地推斷出多線程程序如何與內(nèi)存交互。

  • It should be possible to design correct, high performance JVM implementations across a wide range of popular hardware architectures.
    應(yīng)該有可能在廣泛的流行硬件體系結(jié)構(gòu)中設(shè)計(jì)正確的高性能 JVM 實(shí)現(xiàn)。

  • A new guarantee of initialization safety should be provided. If an object is properly constructed (which means that references to it do not escape during construction), then all threads which see a reference to that object will also see the values for its final fields that were set in the constructor, without the need for synchronization.
    應(yīng)該提供初始化安全性的新保證。 如果正確構(gòu)造了一個(gè)對(duì)象(這意味著對(duì)該對(duì)象的引用在構(gòu)造期間不會(huì)逸出), 則所有看到對(duì)該對(duì)象的引用的線程也將看到在構(gòu)造函數(shù)中設(shè)置的其 final 字段的值,而無需同步。

  • There should be minimal impact on existing code.
    對(duì)現(xiàn)有代碼的影響應(yīng)該最小。

What is meant by reordering? (重排序是什么意思?)

There are a number of cases in which accesses to program variables (object instance fields, class static fields, and array elements) may appear to execute in a different order than was specified by the program. The compiler is free to take liberties with the ordering of instructions in the name of optimization. Processors may execute instructions out of order under certain circumstances. Data may be moved between registers, processor caches, and main memory in different order than specified by the program.

在許多情況下,對(duì)程序變量(對(duì)象實(shí)例字段,類靜態(tài)字段和數(shù)組元素)的訪問似乎以與程序指定順序不同的順序執(zhí)行。 編譯器以優(yōu)化的名義自由地對(duì)指令進(jìn)行排序。在某些情況下,處理器可能會(huì)無序地執(zhí)行指令。 數(shù)據(jù)可能以與程序指定順序不同的順序在寄存器,處理器高速緩存和主存儲(chǔ)器之間移動(dòng)。

For example, if a thread writes to field a and then to field b, and the value of b does not depend on the value of a, then the compiler is free to reorder these operations, and the cache is free to flush b to main memory before a. There are a number of potential sources of reordering, such as the compiler, the JIT, and the cache.

例如,如果一個(gè)線程先寫入字段 a,然后寫入字段 b,并且 b 的值不取決于 a 的值, 則編譯器可以自由地對(duì)這些操作進(jìn)行重新排序, 并且高速緩存可以在 a 刷到主存之前,自由地將 b 刷到主存。 有許多潛在的重排序源頭,例如編譯器,JIT和緩存。

The compiler, runtime, and hardware are supposed to conspire to create the illusion of as-if-serial semantics, which means that in a single-threaded program, the program should not be able to observe the effects of reorderings. However, reorderings can come into play in incorrectly synchronized multithreaded programs, where one thread is able to observe the effects of other threads, and may be able to detect that variable accesses become visible to other threads in a different order than executed or specified in the program.

編譯器、運(yùn)行時(shí)和硬件應(yīng)該合謀來制造 as-if-serial 語義的假象, 這意味著在單線程程序中,程序不應(yīng)能夠觀察到重排序的效果。 但是,重排序可能會(huì)在不正確同步的多線程程序中發(fā)揮作用,在該程序中,一個(gè)線程能夠觀察其他線程的影響,并且可能能夠檢測(cè)到變量訪問對(duì)其他線程可見的順序與程序中執(zhí)行或指定的順序不同。

Most of the time, one thread doesn't care what the other is doing. But when it does, that's what synchronization is for.

大多數(shù)情況下,一個(gè)線程不在乎另一線程在做什么。但是,當(dāng)它這樣做時(shí),那就是同步的目的。

What was wrong with the old memory model? (舊的內(nèi)存模型出了什么問題?)
There were several serious problems with the old memory model. It was difficult to understand, and therefore widely violated. For example, the old model did not, in many cases, allow the kinds of reorderings that took place in every JVM. This confusion about the implications of the old model was what compelled the formation of JSR-133.

舊的內(nèi)存模型存在幾個(gè)嚴(yán)重的問題。 這很難理解,因此被廣泛地違反了。 例如,在許多情況下,舊模型不允許在每個(gè) JVM 中發(fā)生的那種重排序。 關(guān)于舊模型的含義的這種困惑迫使 JSR-133 的形成。

One widely held belief, for example, was that if final fields were used, then synchronization between threads was unnecessary to guarantee another thread would see the value of the field. While this is a reasonable assumption and a sensible behavior, and indeed how we would want things to work, under the old memory model, it was simply not true. Nothing in the old memory model treated final fields differently from any other field -- meaning synchronization was the only way to ensure that all threads see the value of a final field that was written by the constructor. As a result, it was possible for a thread to see the default value of the field, and then at some later time see its constructed value. This means, for example, that immutable objects like String can appear to change their value -- a disturbing prospect indeed.

例如,一個(gè)普遍持有的信念是,如果使用 final 字段,則為了確保另一個(gè)線程將看到該字段的值,在線程之間的同步是不必要的。 盡管這是一個(gè)合理的假設(shè)和明智的行為,甚至確實(shí)是我們希望事情運(yùn)行的方式, 但在舊的內(nèi)存模型下,事實(shí)并非如此。 在舊的內(nèi)存模型中,final 字段與其他字段沒有任何區(qū)別 —— 意味著同步是確保所有線程都能看到構(gòu)造函數(shù)所寫入的 final 字段值的唯一方法。 結(jié)果,線程有可能看到該字段的默認(rèn)值,然后在以后的某個(gè)時(shí)間看到它的構(gòu)造值。 例如,這意味著諸如 String 之類的不可變對(duì)象似乎可以改變其值 —— 這的確是一個(gè)令人不安的圖景。

The old memory model allowed for volatile writes to be reordered with nonvolatile reads and writes, which was not consistent with most developers intuitions about volatile and therefore caused confusion.

舊的內(nèi)存模型允許將 volatile 寫入與 nonvolatile 讀寫進(jìn)行重排序, 這與大多數(shù)開發(fā)人員對(duì) volatile 的直覺并不一致,因此引起了混亂。

Finally, as we shall see, programmers' intuitions about what can occur when their programs are incorrectly synchronized are often mistaken. One of the goals of JSR-133 is to call attention to this fact.

最后,正如我們將要看到的,程序員對(duì)于當(dāng)程序同步不正確時(shí)可能會(huì)發(fā)生什么的直覺通常是錯(cuò)誤的。 JSR-133 的目標(biāo)之一是引起人們對(duì)這一事實(shí)的關(guān)注。

What do you mean by incorrectly synchronized? (你所說的錯(cuò)誤同步是什么意思?)

Incorrectly synchronized code can mean different things to different people. When we talk about incorrectly synchronized code in the context of the Java Memory Model, we mean any code where

  1. there is a write of a variable by one thread,
  2. there is a read of the same variable by another thread and
  3. the write and read are not ordered by synchronization

錯(cuò)誤同步的代碼對(duì)不同的人可能意味著不同的意思。 當(dāng)我們?cè)?Java 內(nèi)存模型的上下文中談?wù)撳e(cuò)誤同步的代碼時(shí), 我們指的是任何代碼,其中

  1. 一個(gè)線程寫了一個(gè)變量,
  2. 另一個(gè)線程讀取了相同的變量,并且
  3. 寫入和讀取未按同步排序

When these rules are violated, we say we have a data race on that variable. A program with a data race is an incorrectly synchronized program.

當(dāng)這些規(guī)則被違反時(shí),我們說我們?cè)谶@個(gè)變量上有一個(gè) 數(shù)據(jù)競(jìng)爭(zhēng) 。 一個(gè)有數(shù)據(jù)競(jìng)爭(zhēng)的程序是一個(gè)沒有正確同步的程序。

What does synchronization do? (同步有什么作用?)

Synchronization has several aspects. The most well-understood is mutual exclusion -- only one thread can hold a monitor at once, so synchronizing on a monitor means that once one thread enters a synchronized block protected by a monitor, no other thread can enter a block protected by that monitor until the first thread exits the synchronized block.

同步有幾個(gè)方面。最容易理解的是互斥 —— 只有一個(gè)線程可以立即持有一個(gè)監(jiān)視器,因此在監(jiān)視器上進(jìn)行同步意味著一旦一個(gè)線程進(jìn)入由一個(gè)監(jiān)視器保護(hù)的同步塊,則其他線程都不能進(jìn)入該監(jiān)視器保護(hù)的塊,直到第一個(gè)線程退出同步塊。

But there is more to synchronization than mutual exclusion. Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor. After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.

但是同步不僅僅是互斥。 同步確保以可預(yù)見的方式,使線程在同步塊之前或期間對(duì)內(nèi)存的寫入對(duì)于在同一監(jiān)視器上同步的其他線程可見。 退出同步塊后,我們 釋放 該監(jiān)視器,其有將緩存刷新到主內(nèi)存的效果, 以便該線程進(jìn)行的寫入對(duì)于其他線程可見。 在我們進(jìn)入一個(gè)同步塊之前,我們需要 獲取 該監(jiān)視器,該監(jiān)視器具有使本地處理器緩存無效的作用,以便可以從主內(nèi)存中重新加載變量。 然后,我們將能夠看到以前釋放中所有可見的寫入。

Discussing this in terms of caches, it may sound as if these issues only affect multiprocessor machines. However, the reordering effects can be easily seen on a single processor. It is not possible, for example, for the compiler to move your code before an acquire or after a release. When we say that acquires and releases act on caches, we are using shorthand for a number of possible effects.

從高速緩存的角度進(jìn)行討論,聽起來似乎這些問題僅影響多處理器計(jì)算機(jī)。 但是,重排序效果可以在單個(gè)處理器上輕松看到。 例如,編譯器不可能在獲取之前或釋放之后移動(dòng)代碼。 當(dāng)我們說獲取和釋放作用于緩存時(shí),我們使用簡(jiǎn)寫來表示多種可能的影響。

The new memory model semantics create a partial ordering on memory operations (read field, write field, lock, unlock) and other thread operations (start and join), where some actions are said to happen before other operations. When one action happens before another, the first is guaranteed to be ordered before and visible to the second. The rules of this ordering are as follows:

新的內(nèi)存模型語義在內(nèi)存操作(讀字段,寫字段,鎖定,解鎖)和其他線程操作( start 和 join )上創(chuàng)建了部分排序,其中某些操作據(jù)說 happen before 其他操作。 當(dāng)一個(gè)動(dòng)作在另一個(gè)動(dòng)作之前發(fā)生時(shí),第一個(gè)動(dòng)作被確保排序在第二個(gè)動(dòng)作之前并且對(duì)于第二個(gè)動(dòng)作可見。 此排序規(guī)則如下:

  • Each action in a thread happens before every action in that thread that comes later in the program's order.
    線程中的每個(gè)動(dòng)作先于該線程中的在程序順序上后出現(xiàn)的每個(gè)動(dòng)作發(fā)生。

  • An unlock on a monitor happens before every subsequent lock on that same monitor.
    監(jiān)視器上的一個(gè)解鎖發(fā)生在 同一個(gè) 監(jiān)視器上的每個(gè)后續(xù)鎖定之前。

  • A write to a volatile field happens before every subsequent read of that same volatile.
    對(duì) volatile 字段的每個(gè)寫操作發(fā)生在每次后續(xù)讀取 同一個(gè) volatile之前。

  • A call to start() on a thread happens before any actions in the started thread.
    一個(gè)對(duì)線程的 start() 的調(diào)用發(fā)生在被啟動(dòng)線程中的任何操作之前。

  • All actions in a thread happen before any other thread successfully returns from a join() on that thread.
    線程中的所有操作發(fā)生在其他線程成功從該線程上的 join() 返回之前。

This means that any memory operations which were visible to a thread before exiting a synchronized block are visible to any thread after it enters a synchronized block protected by the same monitor, since all the memory operations happen before the release, and the release happens before the acquire.

這意味著線程在退出同步塊之前對(duì)一個(gè)線程可見的任何內(nèi)存操作,在進(jìn)入受同一監(jiān)視器保護(hù)的同步塊之后對(duì)于任何線程都是可見的,因?yàn)樗袃?nèi)存操作都發(fā)生在釋放之前,而釋放發(fā)生在獲取之前。

Another implication is that the following pattern, which some people use to force a memory barrier, doesn't work:

另一個(gè)含義是,某些人用來強(qiáng)制執(zhí)行內(nèi)存屏障的以下模式不起作用:

synchronized (new Object()) {}

This is actually a no-op, and your compiler can remove it entirely, because the compiler knows that no other thread will synchronize on the same monitor. You have to set up a happens-before relationship for one thread to see the results of another.

這實(shí)際上是一個(gè) no-op, 你的編譯器可以完全刪除它,因?yàn)榫幾g器知道沒有其他線程可以在同一監(jiān)視器上同步。 你必須為一個(gè)線程設(shè)置一個(gè) happens-before 關(guān)系,才能查看另一個(gè)線程的結(jié)果。

Important Note: Note that it is important for both threads to synchronize on the same monitor in order to set up the happens-before relationship properly. It is not the case that everything visible to thread A when it synchronizes on object X becomes visible to thread B after it synchronizes on object Y. The release and acquire have to "match" (i.e., be performed on the same monitor) to have the right semantics. Otherwise, the code has a data race.

重要說明: 請(qǐng)注意,兩個(gè)線程必須在同一監(jiān)視器上同步,以便正確設(shè)置 happens-before 關(guān)系。 當(dāng)線程A在對(duì)象X上同步時(shí),對(duì)于線程A可見的所有東西,在線程B在對(duì)象y上同步后都是可見的,并不是這樣的。釋放和獲取必須“匹配”(即,在同一監(jiān)視器上執(zhí)行)才能具有正確的語義。否則,代碼將發(fā)生數(shù)據(jù)爭(zhēng)用。

How can final fields appear to change their values? (final 字段如何改變 他們的值?)

One of the best examples of how final fields' values can be seen to change involves one particular implementation of the String class.

關(guān)于如何看待 final 字段值更改的最佳示例之一涉及 String 類的一種特定實(shí)現(xiàn)。

A String can be implemented as an object with three fields -- a character array, an offset into that array, and a length. The rationale for implementing String this way, instead of having only the character array, is that it lets multiple String and StringBuffer objects share the same character array and avoid additional object allocation and copying. So, for example, the method String.substring() can be implemented by creating a new string which shares the same character array with the original String and merely differs in the length and offset fields. For a String, these fields are all final fields.

一個(gè) String 可以實(shí)現(xiàn)為具有三個(gè)字段的對(duì)象 —— 一個(gè)字符數(shù)組,該數(shù)組的偏移量和長(zhǎng)度。 以這種方式實(shí)現(xiàn) String 的原理,而不是僅擁有字符數(shù)組,是因?yàn)樗试S多個(gè) String 和 StringBuffer 對(duì)象共享同一字符數(shù)組,并避免了額外的對(duì)象分配和復(fù)制。 因此,例如,可以通過創(chuàng)建一個(gè)新字符串來實(shí)現(xiàn) String.substring() 方法,該新字符串與原始 String 共享相同的字符數(shù)組,并且僅僅在長(zhǎng)度和偏移量字段方面不同。 對(duì)于一個(gè) String,這些字段都是 final 字段。

String s1 = "/usr/tmp";
String s2 = s1.substring(4); 

The string s2 will have an offset of 4 and a length of 4. But, under the old model, it was possible for another thread to see the offset as having the default value of 0, and then later see the correct value of 4, it will appear as if the string "/usr" changes to "/tmp".

字符串 s2 的偏移量為 4,長(zhǎng)度為 4。但是,在舊模型下,另一個(gè)線程可能會(huì)將偏移量視為默認(rèn)值 0,然后再看到正確的值 4,這樣看起來就像字符串 "/usr" 更改為 "/tmp" 一樣。

The original Java Memory Model allowed this behavior; several JVMs have exhibited this behavior. The new Java Memory Model makes this illegal.

原始的Java內(nèi)存模型允許這種行為。 一些JVM已經(jīng)表現(xiàn)出了這種行為。 新的Java內(nèi)存模型使此操作非法。

How do final fields work under the new JMM? (在新的 JMM 下 final 字段如何工作?)

The values for an object's final fields are set in its constructor. Assuming the object is constructed "correctly", once an object is constructed, the values assigned to the final fields in the constructor will be visible to all other threads without synchronization. In addition, the visible values for any other object or array referenced by those final fields will be at least as up-to-date as the final fields.

對(duì)象 final 字段的值在其構(gòu)造函數(shù)中設(shè)置。 假設(shè)對(duì)象是“正確”構(gòu)造的,則一旦構(gòu)造了對(duì)象,分配給構(gòu)造函數(shù)中 final 字段的值將對(duì)所有其他線程可見,而無需同步。 另外,那些 final 字段引用的任何其他對(duì)象或數(shù)組的可見值,將至少與 final 字段一樣最新。

What does it mean for an object to be properly constructed? It simply means that no reference to the object being constructed is allowed to "escape" during construction. (See Safe Construction Techniques for examples.) In other words, do not place a reference to the object being constructed anywhere where another thread might be able to see it; do not assign it to a static field, do not register it as a listener with any other object, and so on. These tasks should be done after the constructor completes, not in the constructor.

一個(gè)對(duì)象被正確構(gòu)造意味著什么? 它只是意味著在構(gòu)造期間不允許對(duì)正在構(gòu)造的對(duì)象的引用"逃逸"。 (請(qǐng)參閱 Safe Construction Techniques 查看示例。) 換句話說,請(qǐng)勿在其他線程可能看到的地方放置對(duì)正在構(gòu)造的對(duì)象的引用; 不要將其分配給靜態(tài)字段,不要將其注冊(cè)為任何其他對(duì)象的 listener,依此類推。 這些任務(wù)應(yīng)在構(gòu)造函數(shù)完成之后而不是在構(gòu)造函數(shù)中去做。

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}

The class above is an example of how final fields should be used. A thread executing reader is guaranteed to see the value 3 for f.x, because it is final. It is not guaranteed to see the value 4 for y, because it is not final. If FinalFieldExample's constructor looked like this:

上面的類是如何使用 final 字段的示例。 一個(gè)執(zhí)行 reader 的線程被保證可以看到 f.x 的值 3,因?yàn)樗?final。 不能保證 y 的值為 4,因?yàn)樗皇?final。 如果 FinalFieldExample 的構(gòu)造函數(shù)如下所示:

public FinalFieldExample() { // bad!
  x = 3;
  y = 4;
  // bad construction - allowing this to escape
  global.obj = this;
}

then threads that read the reference to this from global.obj are not guaranteed to see 3 for x.

然后,不能保證從 global.obj 讀取對(duì) this 的引用的線程看到 x 的值為 3。

The ability to see the correctly constructed value for the field is nice, but if the field itself is a reference, then you also want your code to see the up to date values for the object (or array) to which it points. If your field is a final field, this is also guaranteed. So, you can have a final pointer to an array and not have to worry about other threads seeing the correct values for the array reference, but incorrect values for the contents of the array. Again, by "correct" here, we mean "up to date as of the end of the object's constructor", not "the latest value available".

查看字段的正確構(gòu)造值的能力很好,但是如果字段本身是引用, 那么你還希望代碼查看其指向的對(duì)象(或數(shù)組)的最新值。 如果你的字段是一個(gè) final 字段,那么這也被保證了。 因此,你可以有一個(gè)指向數(shù)組的 final 指針,而不必?fù)?dān)心其他線程會(huì)看到該數(shù)組引用的正確值,但是看到該數(shù)組內(nèi)容的錯(cuò)誤值。 再一次地,這里的“正確”是指“截至對(duì)象構(gòu)造函數(shù)結(jié)束時(shí)的最新值”,而不是“可用的最新值”。

Now, having said all of this, if, after a thread constructs an immutable object (that is, an object that only contains final fields), you want to ensure that it is seen correctly by all of the other thread, you still typically need to use synchronization. There is no other way to ensure, for example, that the reference to the immutable object will be seen by the second thread. The guarantees the program gets from final fields should be carefully tempered with a deep and careful understanding of how concurrency is managed in your code.

綜上所述,如果在線程構(gòu)造了一個(gè)不可變對(duì)象(即僅包含 final 字段的對(duì)象)之后, 你想要確保所有其他線程都能正確看到該對(duì)象,則通常仍然需要使用同步。 沒有其他方法可以確保,例如,第二個(gè)線程將看到對(duì)不可變對(duì)象的引用。 程序從 final 字段獲得的保證應(yīng)該在深入和仔細(xì)理解代碼中如何管理并發(fā)性的基礎(chǔ)上加以調(diào)整。

There is no defined behavior if you want to use JNI to change final fields.

如果要使用 JNI 更改 final 字段,則沒有定義的行為。

What does volatile do? (volatile 有什么作用?)

Volatile fields are special fields which are used for communicating state between threads. Each read of a volatile will see the last write to that volatile by any thread; in effect, they are designated by the programmer as fields for which it is never acceptable to see a "stale" value as a result of caching or reordering. The compiler and runtime are prohibited from allocating them in registers. They must also ensure that after they are written, they are flushed out of the cache to main memory, so they can immediately become visible to other threads. Similarly, before a volatile field is read, the cache must be invalidated so that the value in main memory, not the local processor cache, is the one seen. There are also additional restrictions on reordering accesses to volatile variables.

Volatile 字段是用于在線程之間傳遞狀態(tài)的特殊字段。 每次讀取 volatile 時(shí),都會(huì)看到由任一線程對(duì)該 volatile 的最后一次寫入; 實(shí)際上,程序員將它們指定為無法接受由于緩存或重排序而導(dǎo)致的“過時(shí)”值的字段。 禁止編譯器和運(yùn)行時(shí)在寄存器中分配它們。 它們還必須確保在寫入后將其從緩存中刷新到主存,以便它們可以立即對(duì)其他線程可見。 同樣,在讀取一個(gè) volatile 字段之前,必須使高速緩存無效,以便可以看到主存儲(chǔ)器中的值而不是本地處理器高速緩存中的值。 在重排列對(duì) volatile 變量的訪問方面還存在其他限制。

Under the old memory model, accesses to volatile variables could not be reordered with each other, but they could be reordered with nonvolatile variable accesses. This undermined the usefulness of volatile fields as a means of signaling conditions from one thread to another.

在舊的內(nèi)存模型下, 對(duì) volatile 變量的訪問不能相互重排序,但可以與 nonvolatile 變量進(jìn)行重排序。 這破壞了 volatile 字段作為從一個(gè)線程到另一個(gè)線程發(fā)條件信號(hào)的一種手段。

Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.

在新的內(nèi)存模型下,volatile 變量不能相互重新排序仍然是正確的。 區(qū)別在于,現(xiàn)在對(duì)它們周圍的普通字段訪問進(jìn)行重排序不再那么容易了。 對(duì)一個(gè) volatile 字段的寫入具有與監(jiān)視器釋放相同的內(nèi)存效果, 而從一個(gè) volatile 字段讀取具有與監(jiān)視器獲取相同的內(nèi)存效果。 實(shí)際上,由于新的內(nèi)存模型對(duì) volatile 字段訪問與其他字段訪問(無論是否為 volatile)的重排序施加了更嚴(yán)格的約束, 因此當(dāng)線程 A 寫入 volatile 字段 f 時(shí),對(duì)線程 A 可見的任何內(nèi)容,在讀取 f 時(shí)對(duì)線程 B 可見。

Here is a simple example of how volatile fields can be used:

這是一個(gè)如何使用 volatile 字段的簡(jiǎn)單示例:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

Assume that one thread is calling writer, and another is calling reader. The write to v in writer releases the write to x to memory, and the read of v acquires that value from memory. Thus, if the reader sees the value true for v, it is also guaranteed to see the write to 42 that happened before it. This would not have been true under the old memory model. If v were not volatile, then the compiler could reorder the writes in writer, and reader's read of x might see 0.

假設(shè)一個(gè)線程在調(diào)用 writer,而另一個(gè)線程在調(diào)用 reader。 在 writer 中對(duì) v 的寫操作會(huì)將對(duì) x 的寫操作釋放到內(nèi)存中, 而對(duì) v 的讀操作則從內(nèi)存中獲取該值。 因此,如果 reader 看到 v 的值為 true,則也可以保證看到在它之前發(fā)生的對(duì) 42 的寫入。 在舊的內(nèi)存模型下,情況并非如此。 如果 v 不是 volatile,則編譯器可以重排序 writer 中的寫入,而reader 對(duì) x 的讀取可能會(huì)看到 0。

Effectively, the semantics of volatile have been strengthened substantially, almost to the level of synchronization. Each read or write of a volatile field acts like "half" a synchronization, for purposes of visibility.

有效地,volatile 的語義已得到實(shí)質(zhì)性增強(qiáng),幾乎達(dá)到了同步的水平。 出于可見性目的,對(duì) volatile 字段的每次讀取或?qū)懭攵碱愃朴凇鞍搿蓖健?/p>

Important Note: Note that it is important for both threads to access the same volatile variable in order to properly set up the happens-before relationship. It is not the case that everything visible to thread A when it writes volatile field f becomes visible to thread B after it reads volatile field g. The release and acquire have to "match" (i.e., be performed on the same volatile field) to have the right semantics.

重要說明: 請(qǐng)注意,兩個(gè)線程訪問同一個(gè) volatile 變量很重要,以便正確設(shè)置 happens-before 關(guān)系。 情況并非如此,當(dāng)線程 A 寫入 volatile 字段f時(shí),對(duì)線程 A 可見的所有內(nèi)容, 在線程 B 讀取 volatile 字段 g 之后對(duì)線程 B 可見。 釋放和獲取必須“匹配”(即在相同的 volatile 字段上執(zhí)行)以具有正確的語義。

Does the new memory model fix the "double-checked locking" problem? (新的內(nèi)存模型是否可以解決“雙重檢查鎖定”問題?)

The (infamous) double-checked locking idiom (also called the multithreaded singleton pattern) is a trick designed to support lazy initialization while avoiding the overhead of synchronization. In very early JVMs, synchronization was slow, and developers were eager to remove it -- perhaps too eager. The double-checked locking idiom looks like this:

(臭名昭著的)雙重檢查鎖定習(xí)慣用法(也稱為多線程單例模式)是一種技巧,旨在支持延遲初始化, 同時(shí)避免同步的開銷。 在非常早期的 JVM 中,同步速度很慢,開發(fā)人員渴望刪除同步 —— 也許太渴望了。 雙重檢查鎖定習(xí)慣用法看起來像這樣:

// double-checked-locking - don't do this!

private static Something instance = null;

public Something getInstance() {
  if (instance == null) {
    synchronized (this) {
      if (instance == null)
        instance = new Something();
    }
  }
  return instance;
}

This looks awfully clever -- the synchronization is avoided on the common code path. There's only one problem with it -- it doesn't work. Why not? The most obvious reason is that the writes which initialize instance and the write to the instance field can be reordered by the compiler or the cache, which would have the effect of returning what appears to be a partially constructed Something. The result would be that we read an uninitialized object. There are lots of other reasons why this is wrong, and why algorithmic corrections to it are wrong. There is no way to fix it using the old Java memory model. More in-depth information can be found at Double-checked locking: Clever, but broken and The "Double Checked Locking is broken" declaration

這看起來非常聰明 —— 在公共代碼路徑上避免了同步。 它只有一個(gè)問題 —— 它不起作用。為什么不起作用? 最明顯的原因是,初始化 instance 的寫操作和對(duì) instance 字段的寫操作可能被編譯器或緩存重排序,這將具有返回似乎是部分構(gòu)造的Something的效果。 結(jié)果將是我們讀取了一個(gè)未初始化的對(duì)象。 還有很多其他原因說明為什么這是錯(cuò)誤的,以及為什么對(duì)其進(jìn)行算法校正是錯(cuò)誤的。 無法使用舊的 Java 內(nèi)存模型對(duì)其進(jìn)行修復(fù)。 可以在 Double-checked locking: Clever, but broken The "Double Checked Locking is broken" declaration 中找到更深入的信息

Many people assumed that the use of the volatile keyword would eliminate the problems that arise when trying to use the double-checked-locking pattern. In JVMs prior to 1.5, volatile would not ensure that it worked (your mileage may vary). Under the new memory model, making the instance field volatile will "fix" the problems with double-checked locking, because then there will be a happens-before relationship between the initialization of the Something by the constructing thread and the return of its value by the thread that reads it.

許多人認(rèn)為 volatile 關(guān)鍵字的使用可以消除嘗試使用雙重檢查鎖定模式時(shí)出現(xiàn)的問題。 在 1.5 之前的 JVM 中,volatile 將無法確保其正常工作(你的里程可能會(huì)有所不同)。 在新的內(nèi)存模型下,使 instance 字段是 volatile 的將通過雙重檢查鎖定來“解決”問題, 因?yàn)檫@樣在構(gòu)造線程對(duì) Something 的初始化和讀取它的線程返回它的值之間就會(huì)存在一個(gè) happens-before 關(guān)系。

However, for fans of double-checked locking (and we really hope there are none left), the news is still not good. The whole point of double-checked locking was to avoid the performance overhead of synchronization. Not only has brief synchronization gotten a LOT less expensive since the Java 1.0 days, but under the new memory model, the performance cost of using volatile goes up, almost to the level of the cost of synchronization. So there's still no good reason to use double-checked-locking. Redacted -- volatiles are cheap on most platforms.
已編輯 —— volatiles在大多數(shù)平臺(tái)上都很便宜。

Instead, use the Initialization On Demand Holder idiom, which is thread-safe and a lot easier to understand:

相反,請(qǐng)使用“按需初始化持有者”慣用語,它是線程安全的,并且更容易理解:

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance() {
  return LazySomethingHolder.something;
}

This code is guaranteed to be correct because of the initialization guarantees for static fields; if a field is set in a static initializer, it is guaranteed to be made visible, correctly, to any thread that accesses that class.
由于靜態(tài)字段的初始化保證,因此可以保證該代碼是正確的。 如果在一個(gè)靜態(tài)初始化中設(shè)置了一個(gè)字段,則可以保證該字段對(duì)訪問該類的任何線程正確可見。

What if I'm writing a VM? (如果我正在編寫虛擬機(jī)怎么辦?)

You should look at http://gee.cs.oswego.edu/dl/jmm/cookbook.html .
你應(yīng)該看看 http://gee.cs.oswego.edu/dl/jmm/cookbook.html

Why should I care? (我為什么要在乎?)

Why should you care? Concurrency bugs are very difficult to debug. They often don't appear in testing, waiting instead until your program is run under heavy load, and are hard to reproduce and trap. You are much better off spending the extra effort ahead of time to ensure that your program is properly synchronized; while this is not easy, it's a lot easier than trying to debug a badly synchronized application.

你為什么要在乎呢? 并發(fā)錯(cuò)誤很難調(diào)試。 它們通常不會(huì)出現(xiàn)在測(cè)試中,而是等到你的程序在高負(fù)載下運(yùn)行時(shí)出現(xiàn),并且很難重現(xiàn)和捕獲。 你最好提前花費(fèi)額外的精力來確保程序正確同步; 盡管這并不容易,但比嘗試調(diào)試同步不良的應(yīng)用程序要容易得多。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容