由于本人能力有限,如有錯誤侈询,歡迎指出舌涨。
原文地址:https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
如果你喜歡原文那種板式的話,可以看這個: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)存高速緩存革为, 這可以通過加快對數(shù)據(jù)的訪問速度 (因為數(shù)據(jù)更靠近處理器) 和減少共享內(nèi)存總線上的通信量 (因為本地緩存可以滿足許多內(nèi)存操作扭粱。)來提高性能。內(nèi)存緩存可以極大地提高性能震檩,但是它們帶來了許多新的挑戰(zhàn)琢蛤。 例如蜓堕,當(dāng)兩個處理器同時檢查相同的內(nèi)存位置時會發(fā)生什么? 他們將在什么條件下看到相同的價值博其?
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.
在處理器級別套才,內(nèi)存模型定義了必要條件和充分條件,以便知道其他處理器對內(nèi)存的寫操作對當(dāng)前處理器可見慕淡,和當(dāng)前處理器的寫操作對其他處理器可見背伴。 一些處理器表現(xiàn)出強(qiáng)大的內(nèi)存模型,其中所有處理器始終在任何給定的內(nèi)存位置看到完全相同的值峰髓。 其他處理器表現(xiàn)出較弱的內(nèi)存模型傻寂,其中需要特殊的指令(稱為內(nèi)存屏障)來刷新或使本地處理器緩存無效, 以便該本地處理器看到其他處理器做出的寫入或使該處理器的寫入對其他處理器可見儿普。 這些內(nèi)存屏障通常在執(zhí)行鎖定和解鎖操作時執(zhí)行崎逃; 使用高級語言的程序員看不到它們。
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.
有時為強(qiáng)大的內(nèi)存模型編寫程序可能會更容易眉孩,因為減少了對內(nèi)存屏障的需求。 但是勒葱,即使在某些最強(qiáng)大的內(nèi)存模型上浪汪,也經(jīng)常需要使用內(nèi)存屏障。 它們的放置經(jīng)常違反直覺凛虽。 處理器設(shè)計的最新趨勢鼓勵使用較弱的內(nèi)存模型死遭,因為它們對高速緩存一致性的放寬允許跨多個處理器的更大可伸縮性和更大的內(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)于何時一個寫操作對另一個線程可見的問題被編譯器對代碼的重新序復(fù)雜化了凯旋。 例如呀潭,編譯器可能認(rèn)為把寫操作移到程序的后面會更有效;只要這個代碼移動不改變程序的語義,編譯器可以自由地這樣做至非。 如果一個編譯器延遲了一個操作钠署,另一個線程將看不到它,直到它被執(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)存的操作可以在程序中被提前移動; 在這種情況下谐鼎,其他線程可能會在該操作在程序中實際“發(fā)生”之前看到該操作。 所有這些靈活性都是設(shè)計出來的 —— 通過在內(nèi)存模型的范圍內(nèi)給編譯器趣惠、運(yùn)行時或硬件靈活性以最佳順序執(zhí)行操作狸棍,我們可以實現(xiàn)更高的性能。
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.
假設(shè)此代碼是在兩個線程中同時執(zhí)行的味悄,而 y 的讀取將看到值 2 草戈。 由于此寫入是在寫入 x 之后完成的,因此程序員可能會認(rèn)為 x 的讀取必須看到值 1 侍瑟。但是唐片,寫入可能已被重排序。 如果發(fā)生這種情況,則可能發(fā)生對 y 的寫入牵触,隨后是兩個變量的讀取淮悼,然后可能發(fā)生對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)行交互钉汗。 它描述了程序中的變量與在真實計算機(jī)系統(tǒng)中的存儲器或寄存器進(jìn)行存儲和獲取變量的底層細(xì)節(jié)之間的關(guān)系羹令。 它以這樣一種方式來實現(xiàn)上面要求,該方式使用各種硬件和各種編譯器優(yōu)化來正確實現(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è)計時并沒有直接支持多線程。 這些語言對發(fā)生在編譯器和體系結(jié)構(gòu)中的各種重排序所提供的保護(hù)在很大程度上依賴于所使用的線程庫(例如 pthreads )汉匙、 所使用的編譯器和運(yùn)行代碼的平臺所提供的保證
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)了幾個嚴(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)存模型是一個雄心勃勃的事業(yè)绎秒。 這是編程語言規(guī)范首次嘗試納入一種內(nèi)存模型浦妄,該模型可以為各種體系結(jié)構(gòu)中的并發(fā)提供一致的語義。 不幸的是见芹,事實證明剂娄,定義一個既一致又直觀的內(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)同步之類的看似簡單的概念到底有多復(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)其他安全保證羽戒。 例如,可能不會“憑空”創(chuàng)建變量值:某個線程觀察到的變量的每個值必須是某個線程可以合理放置在其中的值虎韵。The semantics of correctly synchronized programs should be as simple and intuitive as possible.
正確同步的程序的語義應(yīng)盡可能簡單直觀易稠。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è)計正確的高性能 JVM 實現(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òu)造期間不會逸出)衬吆, 則所有看到對該對象的引用的線程也將看到在構(gòu)造函數(shù)中設(shè)置的其 final 字段的值,而無需同步绳泉。There should be minimal impact on existing code.
對現(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.
在許多情況下零酪,對程序變量(對象實例字段,類靜態(tài)字段和數(shù)組元素)的訪問似乎以與程序指定順序不同的順序執(zhí)行拇勃。 編譯器以優(yōu)化的名義自由地對指令進(jìn)行排序四苇。在某些情況下,處理器可能會無序地執(zhí)行指令方咆。 數(shù)據(jù)可能以與程序指定順序不同的順序在寄存器月腋,處理器高速緩存和主存儲器之間移動。
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.
例如瓣赂,如果一個線程先寫入字段 a榆骚,然后寫入字段 b,并且 b 的值不取決于 a 的值煌集, 則編譯器可以自由地對這些操作進(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)行時和硬件應(yīng)該合謀來制造 as-if-serial 語義的假象祝高, 這意味著在單線程程序中,程序不應(yīng)能夠觀察到重排序的效果污筷。 但是工闺,重排序可能會在不正確同步的多線程程序中發(fā)揮作用,在該程序中颓屑,一個線程能夠觀察其他線程的影響斤寂,并且可能能夠檢測到變量訪問對其他線程可見的順序與程序中執(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ù)情況下揪惦,一個線程不在乎另一線程在做什么遍搞。但是,當(dāng)它這樣做時器腋,那就是同步的目的溪猿。
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)存模型存在幾個嚴(yán)重的問題纫塌。 這很難理解诊县,因此被廣泛地違反了。 例如措左,在許多情況下依痊,舊模型不允許在每個 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.
例如猴蹂,一個普遍持有的信念是疏尿,如果使用 final 字段弟头,則為了確保另一個線程將看到該字段的值阁危,在線程之間的同步是不必要的。 盡管這是一個合理的假設(shè)和明智的行為状飞,甚至確實是我們希望事情運(yùn)行的方式毫胜, 但在舊的內(nèi)存模型下,事實并非如此诬辈。 在舊的內(nèi)存模型中酵使,final 字段與其他字段沒有任何區(qū)別 —— 意味著同步是確保所有線程都能看到構(gòu)造函數(shù)所寫入的 final 字段值的唯一方法。 結(jié)果自晰,線程有可能看到該字段的默認(rèn)值凝化,然后在以后的某個時間看到它的構(gòu)造值。 例如酬荞,這意味著諸如 String 之類的不可變對象似乎可以改變其值 —— 這的確是一個令人不安的圖景搓劫。
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ā)人員對 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.
最后勤揩,正如我們將要看到的,程序員對于當(dāng)程序同步不正確時可能會發(fā)生什么的直覺通常是錯誤的秘蛔。 JSR-133 的目標(biāo)之一是引起人們對這一事實的關(guān)注陨亡。
What do you mean by incorrectly synchronized? (你所說的錯誤同步是什么意思?)
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
- there is a write of a variable by one thread,
- there is a read of the same variable by another thread and
- the write and read are not ordered by synchronization
錯誤同步的代碼對不同的人可能意味著不同的意思深员。 當(dāng)我們在 Java 內(nèi)存模型的上下文中談?wù)撳e誤同步的代碼時负蠕, 我們指的是任何代碼,其中
- 一個線程寫了一個變量倦畅,
- 另一個線程讀取了相同的變量遮糖,并且
- 寫入和讀取未按同步排序
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ù)據(jù)競爭 叠赐。 一個有數(shù)據(jù)競爭的程序是一個沒有正確同步的程序欲账。
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.
同步有幾個方面芭概。最容易理解的是互斥 —— 只有一個線程可以立即持有一個監(jiān)視器赛不,因此在監(jiān)視器上進(jìn)行同步意味著一旦一個線程進(jìn)入由一個監(jiān)視器保護(hù)的同步塊,則其他線程都不能進(jìn)入該監(jiān)視器保護(hù)的塊罢洲,直到第一個線程退出同步塊踢故。
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ù)見的方式惹苗,使線程在同步塊之前或期間對內(nèi)存的寫入對于在同一監(jiān)視器上同步的其他線程可見畴椰。 退出同步塊后,我們 釋放 該監(jiān)視器鸽粉,其有將緩存刷新到主內(nèi)存的效果, 以便該線程進(jìn)行的寫入對于其他線程可見抓艳。 在我們進(jìn)入一個同步塊之前触机,我們需要 獲取 該監(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ī)。 但是位他,重排序效果可以在單個處理器上輕松看到氛濒。 例如产场,編譯器不可能在獲取之前或釋放之后移動代碼。 當(dāng)我們說獲取和釋放作用于緩存時舞竿,我們使用簡寫來表示多種可能的影響京景。
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)一個動作在另一個動作之前發(fā)生時鄙皇,第一個動作被確保排序在第二個動作之前并且對于第二個動作可見。 此排序規(guī)則如下:
Each action in a thread happens before every action in that thread that comes later in the program's order.
線程中的每個動作先于該線程中的在程序順序上后出現(xiàn)的每個動作發(fā)生仰挣。An unlock on a monitor happens before every subsequent lock on that same monitor.
監(jiān)視器上的一個解鎖發(fā)生在 同一個 監(jiān)視器上的每個后續(xù)鎖定之前伴逸。A write to a volatile field happens before every subsequent read of that same volatile.
對 volatile 字段的每個寫操作發(fā)生在每次后續(xù)讀取 同一個 volatile之前。A call to start() on a thread happens before any actions in the started thread.
一個對線程的 start() 的調(diào)用發(fā)生在被啟動線程中的任何操作之前椎木。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.
這意味著線程在退出同步塊之前對一個線程可見的任何內(nèi)存操作,在進(jìn)入受同一監(jiān)視器保護(hù)的同步塊之后對于任何線程都是可見的香椎,因為所有內(nèi)存操作都發(fā)生在釋放之前漱竖,而釋放發(fā)生在獲取之前。
Another implication is that the following pattern, which some people use to force a memory barrier, doesn't work:
另一個含義是畜伐,某些人用來強(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.
這實際上是一個 no-op馍惹, 你的編譯器可以完全刪除它,因為編譯器知道沒有其他線程可以在同一監(jiān)視器上同步玛界。 你必須為一個線程設(shè)置一個 happens-before 關(guān)系万矾,才能查看另一個線程的結(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.
重要說明: 請注意慎框,兩個線程必須在同一監(jiān)視器上同步良狈,以便正確設(shè)置 happens-before 關(guān)系。 當(dāng)線程A在對象X上同步時笨枯,對于線程A可見的所有東西薪丁,在線程B在對象y上同步后都是可見的,并不是這樣的馅精。釋放和獲取必須“匹配”(即严嗜,在同一監(jiān)視器上執(zhí)行)才能具有正確的語義。否則洲敢,代碼將發(fā)生數(shù)據(jù)爭用漫玄。
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 類的一種特定實現(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.
一個 String 可以實現(xiàn)為具有三個字段的對象 —— 一個字符數(shù)組睦优,該數(shù)組的偏移量和長度渗常。 以這種方式實現(xiàn) String 的原理,而不是僅擁有字符數(shù)組刨秆,是因為它允許多個 String 和 StringBuffer 對象共享同一字符數(shù)組凳谦,并避免了額外的對象分配和復(fù)制。 因此衡未,例如尸执,可以通過創(chuàng)建一個新字符串來實現(xiàn) String.substring() 方法,該新字符串與原始 String 共享相同的字符數(shù)組缓醋,并且僅僅在長度和偏移量字段方面不同如失。 對于一個 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褪贵,長度為 4。但是抗俄,在舊模型下脆丁,另一個線程可能會將偏移量視為默認(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.
對象 final 字段的值在其構(gòu)造函數(shù)中設(shè)置茸塞。 假設(shè)對象是“正確”構(gòu)造的躲庄,則一旦構(gòu)造了對象,分配給構(gòu)造函數(shù)中 final 字段的值將對所有其他線程可見钾虐,而無需同步噪窘。 另外,那些 final 字段引用的任何其他對象或數(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òu)造意味著什么? 它只是意味著在構(gòu)造期間不允許對正在構(gòu)造的對象的引用"逃逸"。 (請參閱 Safe Construction Techniques 查看示例荡短。) 換句話說,請勿在其他線程可能看到的地方放置對正在構(gòu)造的對象的引用哆键; 不要將其分配給靜態(tài)字段掘托,不要將其注冊為任何其他對象的 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 字段的示例弯院。 一個執(zhí)行 reader 的線程被保證可以看到 f.x 的值 3,因為它是 final泪掀。 不能保證 y 的值為 4听绳,因為它不是 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 讀取對 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)造值的能力很好,但是如果字段本身是引用塔拳, 那么你還希望代碼查看其指向的對象(或數(shù)組)的最新值鼠证。 如果你的字段是一個 final 字段,那么這也被保證了靠抑。 因此量九,你可以有一個指向數(shù)組的 final 指針,而不必?fù)?dān)心其他線程會看到該數(shù)組引用的正確值颂碧,但是看到該數(shù)組內(nèi)容的錯誤值荠列。 再一次地,這里的“正確”是指“截至對象構(gòu)造函數(shù)結(jié)束時的最新值”载城,而不是“可用的最新值”肌似。
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)造了一個不可變對象(即僅包含 final 字段的對象)之后个曙, 你想要確保所有其他線程都能正確看到該對象锈嫩,則通常仍然需要使用同步。 沒有其他方法可以確保垦搬,例如呼寸,第二個線程將看到對不可變對象的引用。 程序從 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 時,都會看到由任一線程對該 volatile 的最后一次寫入栅干; 實際上迈套,程序員將它們指定為無法接受由于緩存或重排序而導(dǎo)致的“過時”值的字段。 禁止編譯器和運(yùn)行時在寄存器中分配它們碱鳞。 它們還必須確保在寫入后將其從緩存中刷新到主存桑李,以便它們可以立即對其他線程可見。 同樣,在讀取一個 volatile 字段之前贵白,必須使高速緩存無效率拒,以便可以看到主存儲器中的值而不是本地處理器高速緩存中的值。 在重排列對 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)存模型下猬膨, 對 volatile 變量的訪問不能相互重排序,但可以與 nonvolatile 變量進(jìn)行重排序呛伴。 這破壞了 volatile 字段作為從一個線程到另一個線程發(fā)條件信號的一種手段勃痴。
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)在對它們周圍的普通字段訪問進(jìn)行重排序不再那么容易了。 對一個 volatile 字段的寫入具有與監(jiān)視器釋放相同的內(nèi)存效果褐隆, 而從一個 volatile 字段讀取具有與監(jiān)視器獲取相同的內(nèi)存效果污它。 實際上,由于新的內(nèi)存模型對 volatile 字段訪問與其他字段訪問(無論是否為 volatile)的重排序施加了更嚴(yán)格的約束庶弃, 因此當(dāng)線程 A 寫入 volatile 字段 f 時衫贬,對線程 A 可見的任何內(nèi)容,在讀取 f 時對線程 B 可見歇攻。
Here is a simple example of how volatile fields can be used:
這是一個如何使用 volatile 字段的簡單示例:
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è)一個線程在調(diào)用 writer固惯,而另一個線程在調(diào)用 reader。 在 writer 中對 v 的寫操作會將對 x 的寫操作釋放到內(nèi)存中缴守, 而對 v 的讀操作則從內(nèi)存中獲取該值葬毫。 因此,如果 reader 看到 v 的值為 true屡穗,則也可以保證看到在它之前發(fā)生的對 42 的寫入贴捡。 在舊的內(nèi)存模型下,情況并非如此村砂。 如果 v 不是 volatile烂斋,則編譯器可以重排序 writer 中的寫入,而reader 對 x 的讀取可能會看到 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 的語義已得到實質(zhì)性增強(qiáng),幾乎達(dá)到了同步的水平评腺。 出于可見性目的帘瞭,對 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.
重要說明: 請注意,兩個線程訪問同一個 volatile 變量很重要蒿讥,以便正確設(shè)置 happens-before 關(guān)系图张。 情況并非如此锋拖,當(dāng)線程 A 寫入 volatile 字段f時,對線程 A 可見的所有內(nèi)容祸轮, 在線程 B 讀取 volatile 字段 g 之后對線程 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í)慣用法(也稱為多線程單例模式)是一種技巧,旨在支持延遲初始化舷夺, 同時避免同步的開銷苦酱。 在非常早期的 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
這看起來非常聰明 —— 在公共代碼路徑上避免了同步。 它只有一個問題 —— 它不起作用敢伸。為什么不起作用扯饶? 最明顯的原因是,初始化 instance 的寫操作和對 instance 字段的寫操作可能被編譯器或緩存重排序池颈,這將具有返回似乎是部分構(gòu)造的Something的效果尾序。 結(jié)果將是我們讀取了一個未初始化的對象。 還有很多其他原因說明為什么這是錯誤的躯砰,以及為什么對其進(jìn)行算法校正是錯誤的每币。 無法使用舊的 Java 內(nèi)存模型對其進(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)鍵字的使用可以消除嘗試使用雙重檢查鎖定模式時出現(xiàn)的問題琢歇。 在 1.5 之前的 JVM 中兰怠,volatile 將無法確保其正常工作(你的里程可能會有所不同)。 在新的內(nèi)存模型下李茫,使 instance 字段是 volatile 的將通過雙重檢查鎖定來“解決”問題揭保, 因為這樣在構(gòu)造線程對 Something 的初始化和讀取它的線程返回它的值之間就會存在一個 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ù)平臺上都很便宜涌矢。
Instead, use the Initialization On Demand Holder idiom, which is thread-safe and a lot easier to understand:
相反掖举,請使用“按需初始化持有者”慣用語,它是線程安全的娜庇,并且更容易理解:
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)字段的初始化保證塔次,因此可以保證該代碼是正確的。 如果在一個靜態(tài)初始化中設(shè)置了一個字段名秀,則可以保證該字段對訪問該類的任何線程正確可見励负。
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ā)錯誤很難調(diào)試。 它們通常不會出現(xiàn)在測試中略吨,而是等到你的程序在高負(fù)載下運(yùn)行時出現(xiàn)集币,并且很難重現(xiàn)和捕獲。 你最好提前花費額外的精力來確保程序正確同步翠忠; 盡管這并不容易鞠苟,但比嘗試調(diào)試同步不良的應(yīng)用程序要容易得多。