简体   繁体   English

无法理解Java规范中volatile的示例

[英]Can't understand example of volatile in Java specification

I got general understanding what volatile means in Java. 我大致了解了Java中的volatile意味着什么。 But reading Java SE Specification 8.3.1.4 I have a problem understanding the text beneath that certain volatile example. 但阅读Java SE规范8.3.1.4我在理解某个易变性示例下面的文本时遇到了问题。

class Test {
    static volatile int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for i and j occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread. 这允许方法1和方法2同时执行,但是保证对i和j的共享值的访问完全出现次数,并且以完全相同的顺序出现,因为它们似乎在每个执行程序文本期间发生线。 Therefore, the shared value for j is never greater than that for i, because each update to i must be reflected in the shared value for i before the update to j occurs. 因此,j的共享值永远不会大于i的共享值,因为对i的每个更新必须在更新为j之前反映在i的共享值中。 It is possible, however, that any given invocation of method two might observe a value for j that is much greater than the value observed for i, because method one might be executed many times between the moment when method two fetches the value of i and the moment when method two fetches the value of j. 但是,对于方法二的任何给定调用都可能会观察到j的值远远大于为i观察到的值,因为方法一可能在方法二获取i的值的时刻之间执行多次。方法二取值j的那一刻。

How is 怎么

j never greater than i j永远不会超过我

, but at the same time ,但同时

any given invocation of method two might observe a value for j that is much greater than the value observed for i 方法二的任何给定调用都可能观察到j的值远远大于为i观察到的值

?? ??

Looks like contradiction. 看起来像矛盾。

I got j greater than i after running sample program. 运行示例程序后,我的j大于i Why use volatile then? 为什么要用volatile呢? It gives almost the same result without volatile (also i can be greater than j , one of previous examples in specs). 它使几乎不相同的结果volatile (也i可以大于j ,在规格的先前实例中的一个)。 Why is this example here as an alternative to synchronized ? 为什么这个例子作为synchronized的替代?

At any one time, then j is not greater than i . 在任何时候, j都不大于i

This is different from what method two observes because it is accessing the variables i and j at different times. 这与两个观察到的方法不同,因为它在不同时间访问变量ij i is accessed first, and then j is accessed slightly later. 首先访问i ,然后稍后访问j

This isn't a direct alternative to the synchronized version because the behavior is different. 这不是同步版本的直接替代方法,因为行为不同。 One difference from not using volatile is that without volatile, values of 0 could always be printed. 不使用volatile的一个区别是没有volatile,可以始终打印0值。 The increment doesn't ever need to be visible. 增量不需要是可见的。

The example demonstrates the ordering of volatile accesses. 该示例演示了volatile访问的顺序。 An example that requires this could be something like: 需要这个的例子可能是这样的:

volatile boolean flag = false;
volatile int value;

// Thread 1
if(!flag) {
    value = ...;
    flag = true;
}

// Thread 2
if(flag) {
    System.out.println(value);
    flag = false;
}

and thread 2 reads the value that thread 1 set rather than an old value. 和线程2读取线程1设置的值而不是旧值。

I think the point of the example is to emphasize that you need to take care and ensure the order when using volatile; 我认为这个例子的重点是强调在使用volatile时需要注意并确保顺序; the behavior may be counter-intuitive and the example demonstrates it. 行为可能是违反直觉的,这个例子证明了这一点。

I agree that the wording there is a bit obscure and it is possible to provide more explicit and clear example for multiple cases, but there is no contradiction. 我同意那里的措辞有点模糊,有可能为多个案例提供更明确和清晰的例子,但没有矛盾。

The shared value is the value at the same moment. 共享值是同一时刻的值。 If two threads read values of i and of j at exactly the same moment, the value of j will never be observed greater than i. 如果两个线程在完全相同的时刻读取i和j的值,则永远不会观察到j的值大于i。 volatile guarantees keeping order of reads and updates as in the code. volatile保证保持读取和更新的顺序,如代码中所示。

However, in the sample, print + i and + j are two different operations separated by an arbitrary amount of time; 但是,在样本中,print + i+ j是两个不同的操作,分开了任意的时间量; hence, j can be observed larger than i, because it can be updated arbitrary number of times after the read of i and before the read of j. 因此,可以观察到j大于i,因为在读取i之后和读取j之前可以更新任意次数。

The point of using volatile is that when you concurrently update and access volatile variables with the right order, you can make assumptions that are not possible in principle without volatile. 使用volatile的关键是当你以正确的顺序同时更新和访问volatile变量时,你可以做出原则上不可能没有volatile的假设。

In the sample above, the order of access in two() does not allow to conclude with a confidence which variable is greater or equal. 在上面的示例中, two()中的访问顺序不允许以置信度结束哪个变量大于或等于。

Consider, however, if the sample was changed to System.out.println("j=" + j + " i=" + i); 但是,请考虑样本是否已更改为System.out.println("j=" + j + " i=" + i);

Here you can assert with a confidence that the printed value of j is never larger than the printed value of i. 在这里,您可以放心地断言j的打印值永远不会大于i的打印值。 This assumption will not hold without volatile for two reasons. 由于两个原因,这种假设不会没有volatile

First, updates i++ and j++ can be executed by compiler and hardware in an arbitrary order and in reality may execute as j++;i++. 首先,更新i ++和j ++可以由编译器和硬件以任意顺序执行,实际上可以作为j ++; i ++执行。 If from other thread you then access j and i after j++ but before i++ , you can observe, say, j=1 and i=0 , regardless of the access order. 如果来自其他线程,则在j++之后访问j和i但在i++之前,无论访问顺序如何,您都可以观察到j=1i=0 volatile guarantees that this will not happen and it will execute operations in the order that is written in your source. volatile保证不会发生这种情况,它将按照源代码中的顺序执行操作。

Second, volatile guarantees that another thread will see most recent values changed by another thread, as long as it accesses it in the later point of time after the last update. 其次,volatile保证另一个线程会看到另一个线程更改的最新值,只要它在上次更新后的稍后时间点访问它。 Without volatile, there can be no assumptions about the observed value. 没有挥发性,就没有关于观察值的假设。 In theory, the value can stay for another thread zero forever. 从理论上讲,价值可以永远留在另一个线程零。 The program may print two zeros, zero and an arbitrary number, etc. from past updates; 程序可以从过去的更新中打印两个零,零和任意数字等; the observed value in other threads may be less than the current value that the updater thread sees after an update. 其他线程中的观察值可能小于更新程序线程在更新后看到的当前值。 volatile guarantees that you will see the value in a second thread after the update in the first. volatile保证您将在第一个更新后的第二个线程中看到该值。

While the second guarantee may seem as a consequence of the first (the order guarantee), they are in fact orthogonal. 虽然第二个保证可能看起来是第一个保证(订单保证)的结果,但它们实际上是正交的。

Regarding synchronized , it allows to execute a sequence of non-atomic operations, like i++;j++ as an atomic operation, eg if one thread does synchronized i++;j++ and another does synchronized System.out.println("i=" + i + " j=" + j); 关于synchronized ,它允许执行一系列非原子操作,如i++;j++作为原子操作,例如,如果一个线程同步i++;j++和另一个同步System.out.println("i=" + i + " j=" + j); , the first thread may not perform increment sequence while the second prints and the result will be correct. ,第一个线程可能不执行增量序列,而第二个打印,结果将是正确的。

But this comes at a cost. 但这需要付出代价。 First, synhronized has a performance penalty by itself. 首先,同步化本身具有性能损失。 Second, more important, not always such behavior is required and the blocked thread wastes time, reducing the system throughput (eg you can do so many i++;j++; during System.out). 其次,更重要的是,并不总是需要这样的行为,并且被阻塞的线程会浪费时间,从而降低系统吞吐量(例如,您可以在System.out期间执行许多i ++; j ++;)。

I'd like to propose that it's a mistake and the examples were supposed to print j before i : 我想提出这是一个错误,并且示例应该在i之前打印j

static void two() {
    System.out.println("j=" + j + " i=" + i);
}

The novelty in the first example is that, due to update reordering, j can be greater than i even when observed first . 第一个例子中的新颖性是,由于更新重新排序, 即使首先观察j也可能大于i

The final example now makes perfect sense with some minor edits to the explanation (edits and commentary in brackets): 最后的例子现在很有意义,对解释进行了一些小的修改(括号中的编辑和评论):

This allows method one and method two to be executed concurrently, but guarantees that accesses to the shared values for i and j occur exactly as many times, and in exactly the same order, as they appear to occur during execution of the program text by each thread. 这允许方法one和方法two同时执行,但是保证对ij的共享值的访问完全出现次数,并且以完全相同的顺序出现,因为它们似乎在每个执行程序文本期间发生线。 Therefore, the shared value for j is never [observed to be] greater than that for i , because each update to i must be reflected in the shared value for i before the update to j occurs. 因此,对于共享值j从不[观察到]比更大的i ,因为每个更新i必须在用于共享值反映i更新之前j发生。 It is possible, however, that any given invocation of method two might observe a value for [ i ] that is much greater than the value observed for [ j ], because method one might be executed many times between the moment when method two fetches the value of [ j ] and the moment when method two fetches the value of [ i ]. 但是,对于方法two任何给定调用都可能观察到[ i ]的值远大于[ j ]观察到的值,因为方法one可能在方法two取出之前执行多次。 [ j ]的值和方法two取值[ i ]的时刻。

The key point here is that the second update will never be observed before the first update, when using volatile . 这里的关键点是,在使用volatile时,在第一次更新之前永远不会观察到第二次更新。 The last sentence about the gap between the two reads is entirely parenthetical, and i and j were swapped to conform to the erroneous example. 关于两个读取之间的差距的最后一句话完全是括号,并且ij被交换以符合错误的例子。

How is j never greater than i? j怎么永远不比我大?

Let's say you execute one() only once. 假设您只执行one() During the execution of this method, i is always incremented before j as the increment operations happen one after the other. 在执行此方法期间,i总是在j之前递增,因为递增操作一个接一个地发生。 If you are executing one() concurrently, each individual method call will wait for other methods in the execution queue to finish writing their values to i or j, depending on which variable the currently executing method is trying to increment. 如果同时执行one() ,则每个单独的方法调用将等待执行队列中的其他方法完成将其值写入i或j,具体取决于当前正在执行的方法尝试递增的变量。 So, all writes to i happen one after the other, and all writes to j happen one after the other. 因此,对i的所有写入都是一个接一个地发生,并且对j的所有写入都是一个接一个地发生。 And since within the method body itself i is incremented before j, at a given instant, j will never be greater than i. 并且因为在方法体内本身i在j之前递增,所以在给定时刻,j将永远不会大于i。

any given invocation of method two might observe a value for j that is much greater than the value observed for i, how? 方法二的任何给定调用可能会观察到j的值远远大于为i观察到的值,如何?

If method one() is being executed in the background while you call two() , between the time when i is read and then j is read, the method one can be executed multiple times. 如果在调用two()时在后台执行方法one()则在读取i和读取j之间,方法一可以执行多次。 So, when the value of i is read it could be the result some invocation of one() that happened at time t=0, and when then value of j is read, it could be the result of an invocation of one() that happened later in time, for example at t=10. 因此,当读取i的值时,可能是在时间t = 0时发生的one()调用的结果,并且当读取j值时,它可能是调用one()结果发生在较晚的时间,例如在t = 10时。 Hence, j can be greater than i in this case in the println statement. 因此,在println语句中, j在这种情况下可以大于i

Why use volatile in lieu of synchronized? 为什么使用volatile代替同步?

I will not list all the reasons why anyone should use volatile instead of a synchronized block. 我不会列出为什么任何人应该使用volatile而不是synchronized块的所有原因。 But bear in mind that volatile guarantees atomic access to that particular field alone, and does not ensure the atomic execution of a block of code that is not marked as synchronized . 但请记住, volatile保证对该特定字段的原子访问,并且不能确保未标记为已synchronized的代码块的原子执行。 In this example, access to i and j are synchronized, but the overall operation {i++;j++} isn't synchronized hence it apparently (I use apparently since it is not exactly the same but looks similar) gives the same results as without using the volatile keyword. 在这个例子中,对i和j的访问是同步的,但整体操作{i ++; j ++}没有同步,因此它显然 (我明显使用,因为它不完全相同但看起来相似)给出了与不使用时相同的结果volatile关键字。

How is 怎么

j never greater than i j永远不会超过i

, but at the same time ,但同时

any given invocation of method two might observe a value for j that is much >>greater than the value observed for i 方法二的任何给定调用都可能会观察到j的值,该值远大于对i观察到的值

?? ??

The first statement is always true at any given moment in the program's execution, and the second statement may be true for any given interval in the program's execution. 在程序执行的任何给定时刻 ,第一个语句始终为true,对于程序执行中的任何给定时间间隔 ,第二个语句可能为true。

When a volatile variable is written to, writes to both it and everything before it must become visible to other threads (In Java 5+, at least. The explanation doesn't really change much for versions of Java before that, though). 当写入一个volatile变量时,在它必须对其他线程可见之前写入它和所有内容(至少在Java 5+中。但是之前的解释并没有真正改变Java版本)。 Thus, the increment of i must be visible by the time j is incremented, meaning that j can never appear greater than i to other threads. 因此, i的增量必须在时间j递增时可见,这意味着j对于其他线程看起来永远不会比i

The reads of i and j , though, are not guaranteed to occur at a single moment in the program execution. 但是, ij的读取不能保证在程序执行中的单个时刻发生。 The read of i and j may appear to occur very close to each other to the thread executing two() , but in reality some arbitrary amount of time may have passed between the reads. 对于执行two()的线程, ij的读取可能看起来彼此非常接近,但实际上在读取之间可能已经经过了一些任意的时间量。 For example, two() may read i when i = 5 and j = 5 , but then get "frozen" while other threads execute, changing the values of i and j to, say, 20 and 19 , respectively. 例如,当i = 5j = 5时, two()可以读取i ,但是在其他线程执行时“冻结”,将ij的值分别改为2019 When two() resumes, it picks up where it left off and reads j , which now has a value of 19 . two()恢复时,它会从它停止的地方开始并读取j ,现在它的值为19 two() doesn't re-read i because as far as it is concerned there was no break in execution, so there is no need to undergo the extra work. two()不会重新读取i因为就其而言,执行中没有中断,因此不需要进行额外的工作。

Why use volatile then? 为什么要用volatile呢?

While both volatile and synchronized provide visibility guarantees, the precise semantics are slightly different. 虽然volatilesynchronized提供了可见性保证,但精确的语义略有不同。 volatile guarantees that changes made to the variable will be instantly visible to all threads, while synchronized guarantees that changes made in its block will be visible to all threads as long as they synchronize on the same lock . volatile确保对变量所做的更改将立即对所有线程可见,而synchronized保证其块中所做的更改将对所有线程可见, 只要它们在同一个锁上同步即可 synchronized also provides additional atomicity guarantees that volatile does not. synchronized还提供了volatile不具备的额外原子性保证。

Why is this example here as an alternative to synchronized? 为什么这个例子作为同步的替代?

volatile is a viable alternative to synchronized only if one() is executed by a single thread, which is the case here. volatile是一个可行的替代方法,只有当one()由一个线程执行时才会synchronized ,这就是这里的情况。 In this case, only a single thread is ever writing to i and j , so there is no need for the atomicity guarantees synchronized provides. 在这种情况下,只有一个线程正在写入ij ,因此不需要synchronized提供的原子性保证。 If one() were executed by multiple threads, volatile wouldn't work because the read-add-store operations that make up an increment must occur atomically, and volatile does not guarantee that. 如果one()由多个线程执行, volatile将无法工作,因为组成增量的read-add-store操作必须以原子方式发生,而volatile不保证这一点。

This program does guarantee that method two() observes j >= i-1 (not considering overflow). 该程序确保方法two()观察到j >= i-1 (不考虑溢出)。

Without volatile , the observed values of i,j could be all over the place. 没有volatilei,j的观测值可以遍布整个地方。

The statement 该声明

the shared value for j is never greater than that for i j的共享值永远不会大于i的共享值

is very informal, because it means "at the same time", which is not a defined concept in JMM. 是非正式的,因为它意味着“同时”,这不是JMM中定义的概念。


The core principle of JMM is about "sequential consistency". JMM的核心原则是“顺序一致性”。 The driving motivation of JMM is JMM的动力是

JLS#17 - If a program is correctly synchronized, then all executions of the program will appear to be sequentially consistent JLS#17 - 如果程序正确同步,则程序的所有执行将看起来是顺序一致的

In the following program 在以下程序中

void f()
{
    int x=0, y=0;
    x++;
    print( x>y );
    y++
}

x>y will always be observed as true . x>y将始终被视为true It has to be, if we follow the sequence of actions. 必须是,如果我们遵循一系列行动。 Otherwise, there is really no way for us to reason about any (imperative) code. 否则,我们真的无法推理任何(命令性)代码。 That is "sequential consistency". 那就是“顺序一致性”。

"Sequential consistency" is an observed property, it doesn't have to coincide with "real" actions (whatever that means). “顺序一致性”是一种观察到的属性,它不必与“真实”行为一致(无论这意味着什么)。 It is entirely possible that x>y is evaluated to be true by JVM before x is actually incremented (or at all). x实际递增(或根本不加)之前,完全有可能通过JVM将x>y评估为true As long as JVM can guarantee observed sequential consistency, it can optimize actual execution anyway it can, eg execute code out of order. 只要JVM可以保证观察到的顺序一致性,它就可以优化实际执行,例如,无序执行代码。

But this is for a singlet thread. 但这是针对单线程的。 If multiple threads are reading/writing shared variables, such optimizations of course will completely wreck sequential consistency. 如果多个线程正在读/写共享变量,那么这样的优化当然会完全破坏顺序一致性。 We cannot reason about program behavior by thinking of interleaving actions from multiple threads (with actions in the same thread following intra-thread sequence). 我们无法通过考虑来自多个线程的交错动作来解释程序行为(在线程内序列之后的同一线程中的动作)。

If we want to guarantee inter-thread sequential consistency of any multi-thread code, we must abandon the optimization techniques developed for single thread. 如果我们想要保证任何多线程代码的线程间顺序一致性,我们必须放弃为单线程开发的优化技术。 That is going to have severe performance penalty for most programs. 对于大多数程序来说,这将会导致严重的性能损失。 And it is also uncalled for -- data exchange among threads is rather rare. 它也是不必要的 - 线程之间的数据交换相当罕见。

Therefore, special instructions are created just for establishing inter-thread sequential consistency when it is needed. 因此,创建特殊指令只是为了需要建立线程间顺序一致性。 Volatile reads and writes are such actions. 易失性读写就是这样的行为。 All volatile reads and writes obey inter-thread sequential consistency. 所有易失性读写都遵循线程间顺序一致性。 In this case, it guarantees that j >= i-1 in two() . 在这种情况下,它保证two()中的j >= i-1

All dependes on how you are using it. 所有这些都取决于您如何使用它。 The volatile keyword in Java is used as an indicator to Java compiler and Thread that do not cache value of this variable and always read it from main memory. Java中的volatile关键字用作Java编译器和Thread的指示符,它不缓存此变量的值并始终从主内存中读取它。 So if you want to share any variable in which read and write operation is atomic by implementation eg read and write in an int or a boolean variable then you can declare them as volatile variable. 因此,如果您想通过实现共享任何读取和写入操作都是原子的变量,例如在int或boolean变量中读取和写入,那么您可以将它们声明为volatile变量。

From Java 5 along with major changes like Autoboxing, Enum, Generics and Variable arguments , Java introduces some change in Java Memory Model (JMM), Which guarantees visibility of changes made from one thread to another also as "happens-before" which solves the problem of memory writes that happen in one thread can "leak through" and be seen by another thread. 从Java 5以及Autoboxing,Enum,Generics和Variable参数等主要变化中,Java引入了Java内存模型(JMM)的一些变化,它保证了从一个线程到另一个线程所做的更改的可见性,也就是“之前发生”,这解决了在一个线程中发生的内存写入问题可以“泄漏”并被另一个线程看到。

The Java volatile keyword cannot be used with method or class and it can only be used with a variable. Java volatile关键字不能与方法或类一起使用,它只能与变量一起使用。 Java volatile keyword also guarantees visibility and ordering, after Java 5 write to any volatile variable happens before any read into the volatile variable. Java volatile关键字还保证可见性和排序,在Java 5写入任何volatile变量之后发生任何读入volatile变量之前。 By the way use of volatile keyword also prevents compiler or JVM from the reordering of code or moving away them from synchronization barrier. 顺便说一句,使用volatile关键字也会阻止编译器或JVM重新排序代码或将它们从同步障碍中移开。

Important points on Volatile keyword in Java 关于Java中Volatile关键字的重点

  1. The volatile keyword in Java is only application to a variable and using volatile keyword with class and method is illegal. Java中的volatile关键字只是应用于变量,而使用volatile关键字与类和方法是非法的。

  2. volatile keyword in Java guarantees that value of the volatile variable will always be read from main memory and not from Thread's local cache. Java中的volatile关键字保证volatile变量的值将始终从主内存中读取,而不是从Thread的本地缓存中读取。

  3. In Java reads and writes are atomic for all variables declared using Java volatile keyword (including long and double variables). 在Java中,读取和写入对于使用Java volatile关键字(包括长变量和双变量)声明的所有变量都是原子的。

  4. Using the volatile keyword in Java on variables reduces the risk of memory consistency errors because any write to a volatile variable in Java establishes a happens-before relationship with subsequent reads of that same variable. 在变量上使用Java中的volatile关键字可以降低内存一致性错误的风险,因为在Java中对volatile变量的任何写入都会建立与之后读取同一变量的先发生关系。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM