简体   繁体   English

Java -> 易失性和最终:易失性作为刷新所有内存内容

[英]Java -> volatile and final: Volatile as flushing-all-memory-content

Take a look on that answer here (1):在此处查看该答案(1):

https://stackoverflow.com/a/2964277/2182302 ( Java Concurrency: Volatile vs final in "cascaded" variables? ) https://stackoverflow.com/a/2964277/2182302Java并发:“级联”变量中的易失性与最终性?

and on my old question here (2):关于我的老问题(2):

one java memoryFlushing volatile: A good programdesign? 一个 java 内存Flushing volatile:一个好的程序设计?

So as i understand (see (2)) i can use volatile variables as memory barrier/flusher for ALL memory content not only for the referenced one by the volatile keyword.据我了解(见(2)),我可以将 volatile 变量用作 memory 屏障/冲洗器,用于所有 memory 内容,而不仅仅是 volatile 关键字引用的内容。

now the accepted answer in (1) says that it would only flush the memory where the volatile-keyowrd is attached on.现在(1)中接受的答案说它只会刷新连接了 volatile-keyowrd 的 memory。

So what is correct now?, and if the flushing-all principle in (2) is correct, why i cant then attach volatile to variables in combination with final?那么现在什么是正确的呢?如果(2)中的全部刷新原则是正确的,为什么我不能将 volatile 与 final 结合起来附加到变量上?

Neither answer is correct, because you're thinking about it the wrong way.这两个答案都不正确,因为你的想法是错误的。 The concept of 'flush the memory' is simply made up. '刷新内存'的概念是简单的。 It's nowhere in the Java Virtual Machine Specification.它不在 Java 虚拟机规范中。 It's just Not A Thing.这不是一件事。 Yes, many CPU/architectures do work that way, but the JVM does not.是的,许多 CPU/架构确实以这种方式工作,但 JVM 却没有。

You need to program to the JVM spec.您需要按照 JVM 规范进行编程。 Failure to do so means you write code that works perfectly fine on your machine, every time, and then you upload it to your server and it fails there.不这样做意味着您编写的代码每次都可以在您的机器上完美运行,然后您将其上传到您的服务器并在那里失败。 This is a horrible scenario: Buggy code, but bugs that cannot ever be trigged by tests.这是一个可怕的场景:有错误的代码,但是测试永远无法触发错误。 Yowza, those are bad. Yowza,那些很糟糕。

So, what is in the JVM spec?那么,JVM 规范有什么?

Not the concept of 'flushing'.不是“冲洗”的概念。 What it does have, is the concept of HBHA: Happens-Before/Happens-After.它所拥有的是 HBHA 的概念:Happens-Before/Happens-After。 Here's how it works:以下是它的工作原理:

  1. There is a list of specific interactions which sets up that some line of code is defined to 'happen before' (HB/HA = Happens before/Happens after) some other line.有一个特定交互列表,其中设置了某些代码行被定义为“发生在之前”(HB/HA = 发生在/发生在之后)其他行。 An idea of this list is given below.下面给出了这个列表的一个想法。
  2. For any two lines which have an HBHA relationship, it would be impossible for the HA line to observe any state being such that it appears as if the HB line has not run yet.对于具有 HBHA 关系的任何两条线,HA 线不可能观察到任何 state 看起来好像 HB 线还没有运行。 It's basically saying: HB lines occur before HA lines, except not quite that strong: You cannot observe the opposite (ie HB changes variable X, the HA line does not see this change to X, that'd be observing the opposite, that's impossible).它基本上是在说:HB 线出现在 HA 线之前,除了不是那么强:你不能观察到相反的情况(即 HB 改变变量 X,HA 线没有看到 X 的这种变化,那会观察到相反的情况,这是不可能的)。 Except timing-wise.除了时间方面。 In reality, HB/HA does not actually mean that lines get executed earlier or later: If you have 2 lines with an HB/HA relationship which have no effect on each other (one writes variable X. The other reads completely different variable Y), the JVM/CPU working together is free to reorder as much as it wants.实际上,HB/HA 实际上并不意味着行的执行更早或更晚:如果您有 2 行具有 HB/HA 关系的行彼此没有影响(其中一个写入变量 X。另一个读取完全不同的变量 Y) ,一起工作的 JVM/CPU 可以随意重新排序。
  3. For any two lines with no defined HB/HA relationship, the JVM and CPU are free to do whatever it pleases .对于没有定义 HB/HA 关系的任何两条线,JVM 和 CPU 可以随意做任何事情 Including things that just cannot be explained with a simplistic 'flushing' model.包括无法用简单的“冲洗”model 来解释的事情。

For example:例如:

int a = 0, b = 0;

void thread1() {
    a = 10;
    b = 20;
}

void thread2() {
    System.out.println(b);
    System.out.println(a);
}

In the above, no HB/HA relationship has been established between thread 1 modifying the state of a/b, and thread 2 reading them.在上面,线程1修改a/b的state和线程2读取它们之间没有建立HB/HA关系。

Therefore, it is legal for a JVM to print 20 0 , even though this cannot be explained with basic flushing notions: It is legal for the JVM to 'flush' b but not a.因此, JVM 打印20 0是合法的,尽管这无法用基本的刷新概念来解释:JVM 可以“刷新”b 而不是 a。

It is somewhat unlikely for you to be capable of writing this code and actually observing that 20/0 print on any JVM version or any hardware, but the point is: It is allowed, and some day (or probably, it already exists), some exotic combo of JVM+hardware+OS version+state of the machine combines to actually make this happen, so if your code breaks if this sequence of events occurs, then you wrote a bug .您不太可能编写此代码并实际观察到任何 JVM 版本或任何硬件上的 20/0 打印,但重点是:这是允许的,并且有一天(或者可能,它已经存在), JVM+硬件+操作系统版本+机器状态的一些奇特组合结合起来实际实现了这一点,所以如果你的代码在这一系列事件发生时中断,那么你写了一个错误

In effect, if one line mutates state, and another line reads it, and those 2 lines have no HB/HA, you messed up , and you need to fix your bug.实际上,如果一行发生了变异 state,而另一行读取了它,而这两行没有 HB/HA,那么你搞砸了,你需要修复你的错误。 Even (especially.) if you can't manage to write a test that actually proves it.即使(尤其是)如果您无法编写实际证明它的测试。

The trick here is that volatile reads do establish HB/HA, and as that is the only mechanism that the JVMS spec has to sync stuff up, yes, this has the effect of guaranteeing that you 'see all changes'.这里的技巧是 volatile 读取确实建立了 HB/HA,因为这是 JVMS 规范必须同步内容的唯一机制,是的,这具有保证您“看到所有更改”的效果。 But this is not, at all, a good idea.但这根本不是一个好主意。 Especially because the JVMS also says that the hotspot compiler is free to eliminate lines that have no side-effect.特别是因为 JVMS 还说热点编译器可以自由地消除没有副作用的行。

So now we're going to have to get into a debate on whether 'establishes HBHA' is a side-effect.所以现在我们将不得不就“建立 HBHA”是否是副作用展开辩论。 It probably is, but now we get to the rule of optimizations:可能是这样,但现在我们得到了优化规则:

Write idiomatic code .编写惯用代码

Whenever azul, the openjdk core dev team, etc are looking at improving the considerable optimization chops of the hotspot compiler, they look at real life code .每当 azul、openjdk 核心开发团队等在考虑改进热点编译器的大量优化时,他们都会关注现实生活中的代码 It's like a gigantic pattern matcher: They look for patterns in code and finds ways to optimize them.它就像一个巨大的模式匹配器:它们在代码中寻找模式并找到优化它们的方法。 They don't just write detectors for everything imaginable: They strongly prefer writing optimizers for patterns that commonly show up in real life java code.他们不只是为所有可以想象的事情编写检测器:他们更喜欢为现实生活中常见的模式编写优化器 java 代码。 After all, what possible point is there spending time and effort optimizing a construction that almost no java code actually contains?毕竟,花时间和精力优化几乎没有 java 代码实际上包含的结构有什么可能呢?

This gets us to the fundamental issue with using throw-away volatile reads as a way to establish HB/HA: Nobody does it that way , so the odds that at some point the JVMS is updated (or simply the conflicting rules are 'interpreted' as meaning: Yeah, hotspot can eliminate a pointless read, even if it did establish an HB/HA that is now no longer there) are quite high - you're also far more likely to run into JVM bugs if you do things in unique ways.这让我们遇到了使用一次性易失性读取作为建立 HB/HA 的方式的基本问题:没有人这样做,因此在某些时候更新 JVMS 的可能性(或者只是“解释”冲突的规则意思是:是的,热点可以消除毫无意义的读取,即使它确实建立了一个现在不再存在的 HB/HA)非常高- 如果你以独特的方式做事,你也更有可能遇到 JVM 错误方法。 After all, if you do things in ways that are well trodden, the bug would have been reported and fixed ages ago.毕竟,如果您以惯用的方式做事,那么该错误就会在很久以前就被报告并修复。

How to establish HB/HA:如何建立 HB/HA:

  1. The natural rule: Within a single thread, code cannot be observed to run in any way except sequentially, ie within one thread, all lines have HB/HA with each other in the obvious fashion.自然规则:在单个线程中,不能观察到代码以除顺序之外的任何方式运行,即在一个线程中,所有行以明显的方式彼此具有 HB/HA。

  2. synchronized blocks: If one thread exits a sync block and then another thread enters one on the same reference , then the sync-block-exit in A Happens-Before the sync-block-enter in B.同步块:如果一个线程退出同步块,然后另一个线程在同一引用上进入同步块,则 A 中的同步块退出发生在 B 中的同步块进入之前。

  3. volatile reads and writes.易失性读取和写入。

  4. Some exotic stuff, such as: thread.start() happens-before the first line that thread's run() method, or all code in a thread is guaranteed to HB before thread.yield() on that thread finishes.一些奇异的东西,例如: thread.start()发生在线程的 run() 方法的第一行之前,或者线程中的所有代码在该线程上的thread.yield()完成之前保证为 HB。 These tend to be obvious.这些往往是显而易见的。

Thus, to answer the question, is it good programming design?因此,要回答这个问题,它是好的编程设计吗?

No , it is not.,不是。

Establish HB/HA in the proper ways: Find something appropriate in java.util.concurrent and use it.以正确的方式建立HB/HA:在java.util.concurrent中找到合适的并使用它。 From a simple lock to a queue to a fork/join pool for the entire job.从简单的锁到队列,再到整个作业的 fork/join 池。 Alternatively, stop sharing state.或者,停止共享 state。 Alternatively, share state with mechanisms that are designed for concurrent access in more natural ways than HB/HA is, such as a database (transactions), or a message queue.或者,将 state 与旨在以比 HB/HA 更自然的方式进行并发访问的机制共享,例如数据库(事务)或消息队列。

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

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