据我们所知,通过更改volatile变量,我们仍然确信将读取它的其他线程将接收到新值。这样做的原因很清楚:变量值没有被缓存(当然还有更多的事情要做,但要点是这里的重点)。所以我不清楚,但是non-volatile同步期间变量更改的可见性会发生什么?
一个可靠的消息来源说:
同步强制将数据写入主存,如果一个字段被同步方法或块完全保护,
volatile则不需要声明它以进行并发访问。
所以,问题是:这是怎么发生的,为什么我们现在不需要关键字volatile?我们需要synchronizedgetter 和 setter,还是读同步就足够了?或者也许只是写下来?
让我解释一下关于 getter 和 setter 的问题的一部分:
让代码:
class A {
int n=1;
public void change(){
while (!Thread.interrupted())
synchronized (this){
n=3*n+4;
}
}
}
在main(String[] args)与类相同的包中A:
A a = new A();
new Thread(a::change).start();
TimeUnit.SECONDS.sleep(10);
System.out.println(a.n);
这里出现了问题。
现在让我们忘记变量n可能会溢出,然后我们在 10 秒内休眠后再次变为等于 1。忘记当我们读取时n它可能处于某种“不稳定”状态。
那么,方法中是否有足够的同步change()?它是否保证推入main memory我们的变量n?那些。事实上,问题是这样的:是否真的不会发生在 10 秒的睡眠后a.n我们得到 1 的情况,因为该字段的值仍在缓存中?
如果您将最后一行更改main(String[] args)为:
synchronized (a){
System.out.println(a.n);
}
现在一切都很好,更改的可见性不会有问题吗?同步机制的这方面是如何工作的?那些。change()没有推入main memory,但同步读取所有内容都被立即推入?...或者change()同步是否足够?
在答案的附录中,我将非常感谢看到 Java 文档(翻译成俄语)中的引用以及指向它们的链接。
修饰符
volatile不仅synchronized解决了缓存问题。在多线程编程中仍然需要考虑很多细微差别。例如,重新排序. 在许多情况下,对变量(对象字段、静态字段和数组元素)的访问可以按照与程序中指定的顺序不同的顺序执行。为了优化,编译器可以自由地安排指令。在某些情况下,处理器可能会以不同的顺序执行指令。数据可以在寄存器、处理器高速缓存和主存储器之间以不同于程序中指定的顺序移动。但是JMM通过设置屏障来确保在同步块中保留发生之前的关系在获取和释放锁时。“同步”这个词的语义不言自明。对变量执行同步访问的线程会同步其状态。但是只有在每个人都同步的情况下,才能为每个人真正更新变量状态。另外,别忘了同步不仅是为了解决可见性问题,也是为了解决原子性问题。如果一个线程在访问该字段时捕获了锁,而另一个线程甚至没有尝试捕获它并因此检查其状态,则锁定没有意义,程序将破坏状态。
PS,而不是文档的链接,我将提供一个人的报告的链接,该人的名字已成为该地区家喻户晓的名字。
Aleksey Shipilёv - Java 内存模型语用学,第 1 部分
Aleksey Shipilёv - Java 内存模型语用学,第 2 部分
Aleksey Shipilё - JMM 学位