我的问题涉及Java中字段值的安全发布(正如此处Java multi-threading & Safe Publication所述)。
据我了解,如果出现以下情况,可以安全地读取字段(意味着从多个线程访问将看到正确的值):
如果我的理解是正确的,下面的类应不是线程安全的,因为初始值是在没有这些特性的情况下编写的。但是,我发现很难相信我需要制作first
volatile
,即使它只能通过synchronized
方法进行访问。
public class Foo {
private boolean needsGreeting = true;
public synchronized void greet() {
if (needsGreeting) {
System.out.println("hello");
needsGreeting = false;
}
}
}
我错过了什么吗?以上代码是否正确,如果是,为什么?或者在这种情况下是否有必要first
volatile
或使用final AtomicBoolean
或类似的来从synchronized
访问它方法
(请注意,我要注意,如果初始值是用synchronized
方法编写的,即使没有volatile
关键字,它也会是线程安全的。)
答案 0 :(得分:5)
在构造函数结束和方法调用之间没有发生之前的关系,因此一个线程可以开始构造实例并使引用可用,并且另一个线程可以获取该引用并开始调用部分构造的对象上的greet()方法。 greet()中的同步并没有真正解决这个问题。
如果您通过着名的双重检查锁定模式发布实例,则可以更轻松地查看实例。如果存在这种发生在之前的关系,即使使用DCLP也应该是安全的。
public class Foo {
private boolean needsGreeting = true;
public synchronized void greet() {
if (needsGreeting) {
System.out.println("Hello.");
needsGreeting = false;
}
}
}
class FooUser {
private static Foo foo;
public static Foo getFoo() {
if (foo == null) {
synchronized (FooUser.class) {
if (foo == null) {
foo = new Foo();
}
}
}
return foo;
}
}
如果多个线程同时调用FooUser.getFoo()。greet(),则一个线程可能正在构造Foo实例,但另一个线程可能会过早地找到非空的Foo引用,并调用greet()并查找needGreeting仍然是假的。
Java Concurrency in Practice(3.5)中提到了一个例子。
答案 1 :(得分:3)
严格来说,我认为可以安全地假设needsGreeting
在调用greet
时设置为true。
为了实现这一点,必须在初始写入(在构造对象时发生)和第一次读取(在greet
- 方法中)之间发生关系。但是,JLS中的Chapter 17 Threads and Locks表示以下关于发生前( hb )约束的内容:
17.4.5在订单之前发生 可以通过先发生关系来排序两个动作。如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并且在第二个之前被命令。
如果我们有两个动作x和y,我们写 hb(x,y)来表示x发生在y之前。
- 如果x和y是同一个线程的动作,并且x在程序顺序中位于y之前,那么 hb(x,y)。
- 从对象的构造函数末尾到该对象的终结符(第12.6节)的开头有一个发生前的边缘。
- 如果某个操作x与后续操作y同步,那么我们还有 hb(x,y)。
- 如果 hb(x,y)和 hb(y,z),则 hb(x,z)。
此外,引入 synchronized-with relation 的唯一方法,即同步订单,可以执行以下操作:
同步动作引发与动作的同步关系,定义如下:
- 监视器m上的解锁操作与m上的所有后续锁定操作同步(后续操作根据同步顺序定义)。
- 对volatile变量(第8.3.1.4节)的写入v与任何线程的v的所有后续读取同步(其中后续根据同步顺序定义)。
- 启动线程的操作与其启动的线程中的第一个操作同步。
- 向每个变量写入默认值(零,false或null)与每个线程中的第一个操作同步。虽然在分配包含变量的对象之前将默认值写入变量似乎有点奇怪,但从概念上讲,每个对象都是在程序开始时使用其默认初始化值创建的。
- 线程T1中的最终操作与另一个检测到T1已终止的线程T2中的任何操作同步。 T2可以通过调用T1.isAlive()或T1.join()来完成此任务。
- 如果线程T1中断线程T2,则T1的中断与任何其他线程(包括T2)确定T2已被中断的任何点同步(通过抛出InterruptedException或通过调用Thread.interrupted或Thread.isInterrupted) 。
它没有说“对象的构造发生在对对象上的方法的任何调用之前。然而,发生在之前的关系,指出有一个先发生过的边缘来自对象的构造函数的结尾,该对象的终结符(第12.6节)的开头。,可能是关于不的提示>从对象的构造函数的结尾到任意方法的开头的before-before edge!