问题 喜欢同步到volatile


我读过这个 回答 最后写了以下内容:

你可以用volatile来完成任何事情,但是   反之亦然。

目前尚不清楚。 JLS 8.3.1.4 定义volatile字段如下:

字段可以声明为volatile,在这种情况下是Java Memory Model   确保所有线程都看到变量的一致值   (§17.4)。

因此,volatile字段与内存可见性有关。另外,据我所提到的答案,读取和写入易失性字段是同步的。

同步反过来保证只有一个线程可以访问同步块。正如我所知,它与内存可见性无关。我错过了什么?


13127
2017-09-28 08:06


起源

进入和退出 synchronized block执行内存屏障,确保跨线程可以看到更改。 - Sneftel


答案:


事实上,同步也与内存可见性有关,因为JVM增加了一个 记忆障碍 在synchronized块的出口处。这确保了同步块中线程的写入结果可以保证通过另一个线程的读取可见 一旦 第一个线程已退出synchronized块。

注意 : 按照@PaŭloEbermann的评论,如果另一个线程通过读取内存屏障(例如,通过进入同步块),它们的本地缓存将不会失效,因此它们可能会读取旧值。

同步块的退出是a 之前发生 在这个文档中: http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility

寻找这些提取物:

一个线程写入的结果保证对a可见   只有在写入操作发生之前,才由另一个线程读取   读操作。

监视器的解锁(同步块或方法退出)   发生在每个后续锁定之前(同步块或方法)   那个监视器的入口)。而且因为发生在之前的关系   是传递的,解锁之前线程的所有动作   发生 - 在任何线程锁定之后的所有操作之前发生   监控。


7
2017-09-28 08:28



非常好的答案,非常感谢。没有想到在记忆可见性概念下有更多的一般概念。 - St.Antario
我会理解这一点略有不同:同步块的更改(实际上也是之前)只对其他线程可见,这些线程后来在同一个对象上进行同步(或者有一些其他方式处于“发生后”关系),并非所有其他线程都读取。 - Paŭlo Ebermann
@PaŭloEbermann确实,如果其他线程不同步(或以任何方式激活读取内存屏障),它们的本地缓存将不会失效,因此它们可能会读取旧值。 - Benoît
@PaŭloEbermann:不确定如何清楚准确地写下来。如果您对此问题有更好的解释,请编辑 - Benoît
@ St.Antario,当你读到这个答案时,请记住“happen-before”在Java语言规范(JLS)中有特殊含义。当规范说“A发生在B之前”时,它意味着A是 保证 在B之前发生。因此,当他们说“X只在写入操作发生时 - 在读取之前”时才是真的,他们 别 意味着如果写,X将为真 其实 在读取之前发生:它们意味着只有在写入时X才能保证为真 保证 在阅读之前发生。您可以使用 synchronized 块以保证一个线程中的写入在另一个线程中读取之前发生。 - Solomon Slow


同步和易失性是不同的,但通常它们都用于解决相同的常见问题。

Synchronized是为了确保在给定的时间点只有一个线程将访问共享资源。

然而,这些共享资源通常被声明为volatile,这是因为,如果一个线程已经改变了共享资源值,它也必须在另一个线程中更新。但是没有volatile,运行时只需通过从缓存中读取值来优化代码。所以volatile的作用是,每当任何线程访问volatile时,它都不会从缓存中读取值,而是实际从实际内存中获取它并使用相同的内容。


正在浏览log4j代码,这就是我发现的。

/**
 * Config should be consistent across threads.
 */
protected volatile PrivateConfig config;

2
2017-09-28 08:29



那么为什么要使用“volatile”关键字?你可以获得多线程访问而无需指定volatile或synchronized。 - Akshayraj Kore


那是错的。同步与内存可见性有关。每个线程都有自己的缓存。如果你有一个锁,缓存就是refresehd。如果释放锁定,则缓存将延伸到主存储器。

如果你读一个volatile字段也有刷新,如果你写一个volatile字段就有一个刷新。


1
2017-09-28 08:23





如果多个线程写入共享的volatile变量并且它们还需要使用它的先前值,则它可以创建一个 竞争条件。所以此时你需要使用同步。

...如果两个线程都在读取和写入共享变量,那么使用volatile关键字是不够的。在这种情况下,您需要使用synchronized来保证变量的读取和写入是原子的。读取或写入volatile变量不会阻止线程读取或写入。为此,您必须在关键部分周围使用synchronized关键字。

有关volatile的详细教程,请参阅 “挥发性”并不总是足够的


1
2017-09-28 08:30



对于简单的情况,如线程安全计数器或cas,使用 原子 类型通常比a更容易并且表现更好 synchronized 阻止,特别是在高争用时。 - duckstep