假设我有以下代码
private volatile Service service;
public void setService(Service service) {
this.service = service;
}
public void doWork() {
service.doWork();
}
标记为volatile的修改字段,其值不依赖于先前的状态。所以,这是正确的多线程代码(不要担心Service
实现一分钟。)
据我所知,从内存可见性的角度来看,读取volatile变量就像进入锁定一样。这是因为读取常规变量不能通过读取volatile变量来重新排序。
这是否意味着以下代码是正确的?
private volatile boolean serviceReady = false;
private Service service;
public void setService(Service service) {
this.service = service;
this.serviceReady = true;
}
public void doWork() {
if ( serviceReady ) {
service.doWork();
}
}
答案 0 :(得分:18)
是的,从Java 1.5开始,这段代码是“正确的”。
原子性不是一个问题,有或没有volatile(对象引用的写入是原子的),所以你可以通过任何方式跨越关注列表 - 唯一的开放问题是变化的可见性和'正确性'的订购。
对volatile变量的任何写入都会设置'before-before'关系(新的Java内存模型的关键概念,如JSR-133中所指定的),并且对同一变量进行任何后续读取。这意味着读取线程必须能够看到写入线程可见的所有内容:也就是说,它必须在写入时看到至少它们的“当前”值的所有变量。
我们可以通过查看section 17.4.5 of the Java Language Specification详细解释这一点,特别是以下要点:
所以在你的例子中:
意味着保证'service'设置正确,在这种情况下,一旦serviceReady为true。
您可以使用几乎完全相同的示例看到一些好的文章,一个在IBM DeveloperWorks - 请参阅“新的保证挥发”:
在写入V时A可见的值现在保证对B可见。
和the JSR-133 FAQ的一个,由JSR的作者撰写:
因此,如果读者看到v的值为true,那么也可以保证看到在它之前发生的写入42。在旧的内存模型下,这不可能是真的。如果v不是volatile,那么编译器可以重新排序writer中的写入,读者读取x可能会看到0。
答案 1 :(得分:2)
AFAIK这是正确的代码。
@CPerkins:只使同步的setService
方法不起作用,因为你还需要同步读取。
但是,在这种情况下,一个变量就足够了。为什么需要额外的布尔字段。 E.g。
private volatile Service service;
public void setService(Service service) {
this.service = service;
}
public void doWork() {
if ( service != null ) {
service.doWork();
}
}
鉴于没有人将setService调用到null
。所以你应该进行空检查:
private volatile Service service;
public void setService(Service service) {
if (service == null) throw NullPointerException();
this.service = service;
}
public void doWork() {
if ( service != null ) {
service.doWork();
}
}
答案 2 :(得分:1)
你对volatile
的效果是正确的,所以这应该是正确的,但我对你的设计感到困惑。我不明白为什么你不只是同步setService
- 它可能不经常被调用。如果不止一次调用它,那么“if (serviceReady)
”部分就没有实际意义,因为它仍然是真的,但这没关系,因为如果我理解正确,替换是原子的。
我认为service.doWork()
是线程安全的,是吗?
答案 3 :(得分:0)
从理论上讲,它永远不会奏效。您希望确保两个变量的内存一致性,并且您希望在第一个变量上依赖volatile read
。 volatile只读保证读取线程看到变量的最新值。所以它肯定不如进入锁定(同步)部分那么强大。
实际上,它可能会起作用,具体取决于您使用的JVM对volatile的实现。如果通过刷新所有CPU缓存实现volatile读取,它应该工作。但我已经准备好打赌它不会发生。 Can I force cache coherency on a multicore x86 CPU?是关于这个主题的好读物。
我想说这两个变量只需要一个公共锁(java.util.concurrent.Lock或synchronized)就可以了。
Java Language Specification, Third Edition,有关于volatile的说法:
8.3.1.4 volatile Fields
字段可以声明为volatile,在这种情况下,Java内存模型(第17节)可确保所有线程都看到变量的一致值。
和
17.4.4同步顺序
- 对volatile变量(第8.3.1.4节)的写入v与任何线程的v的所有后续读取同步(其中后续根据同步顺序定义)。
- 在对该字段的每次后续读取之前发生对易失性字段(第8.3.1.4节)的写入。
它没有说明其他变量的可见性效果。