在线程安全单例中,return 是否必须在同步块内
In a Thread Safe Singleton does the return have to be inside the synchronized block
考虑以下代码:
private static Singleton singleton;
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton; // <-- this part is important
}
这是来自 的后续讨论。最初,我认为它是线程安全的。然而,一些受人尊敬的用户认为这不是线程安全的,因为 synchronized
块之外的 return singleton
。然而,其他一些(受人尊敬的)用户则持不同意见。
读完do we need volatile when implementing singleton using double-check locking后,我改变了主意。 (该问题的代码):
private static Singleton instance;
private static Object lock = new Object();
public static Singleton getInstance() {
if(instance == null) {
synchronized (lock) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
(众所周知,为什么第二个代码需要volatile
。)
然而,在再次查看这两个示例之后,我注意到第一个和第二个代码片段之间存在很大差异。在前者上,最外层的 if
在 synchronized
子句内,因此 synchronized
块内的所有线程 运行 将强制发生先行关系( 即, 如果实例设置正确,线程将无法 return null
)或者我错了吗?我希望执行以下操作顺序:
lock monitor
...
unlock monitor
...
read singleton
我注意到所有与第一个代码片段相似的在线示例在 synchronized
块内都有 return ;然而,这可能只是因为在性能方面它是相同的,因为线程必须同步,所以为什么不为了安全起见将 return 放在里面?! .
问题:
return 真的 需要在 synchronized
块内吗?读取 return 语句的单例值是否可以看到 synchronized
块开始之前的单例值?
编辑:我最初的回答是错误的,但我会保留它以显示我的错误所在。
singleton = new Singleton();
中的写入和return singleton
中的读取之间有一个program order
,它建立了所需的保证;即:这是安全的。
在这个:if (singleton == null)
和这个 singleton = new Singleton()
之间有 program order
关系,根据 JLS
,它也带来了 happens-before
顺序。
但是这个 write: singleton = new Singleton();
和这个 read : return singleton;
完全没有关系读起来 活泼 。 JLS
表示,如果出现此类阅读内容,则无法保证。因此,即使您将 new Singleton()
写入 singleton
,也无法保证 return singleton
会读取该写入的值;它仍然可以读取 null
。只有在 read 发生在同一个锁下时才有保证。
制作 singleton
volatile
解决了这个问题,因为您现在针对 singleton = new Singleton()
和 return singleton
创建了一个 synchronizes-with
订单,这隐含地创建了一个 happens-before
现在。
我是这样看的。
Does the return really needs to be inside the synchronized block?
否 return
不需要在 synchronized
块中 除非 可以将 singleton
字段分配到其他地方。但是,没有充分的理由说明 return
不应该在同步块内。如果整个方法都包含在同步中,那么如果我们在 Singleton
class 中,您只需将方法标记为同步即可。如果单例在其他地方被修改,这会更干净、更好。
就为什么它不需要在内部而言,因为您使用的是 synchronized
块,所以在块的开头有一个读屏障,在块的开头有一个写屏障结束,这意味着线程将获得 singleton
的最新值,并且只会分配一次。
读取内存屏障确保线程将看到更新的单例,该单例将是 null
或完全发布的对象。写内存屏障确保对 singleton
的任何更新都将写入主内存,其中包括 Singleton
的完整构造并将其发布到 singleton
字段。程序顺序保证在 synchronized
块中分配的 singleton
将作为相同的值返回,除非在另一个线程中对 singleton
进行了另一个分配,否则它将是未定义的。
如果您执行以下操作,程序顺序将更加有效。我倾向于在 singleton
为 volatile
时执行此操作(使用适当的双重检查锁定代码)。
synchronized (Singleton.class) {
Singleton value = singleton;
if (singleton == null) {
value = new Singleton();
singleton = value;
}
return value;
}
not thread-safe because of the return singleton outside the synchronized block
因为您使用的是 synchronized
块,所以这不是问题。双重检查锁定就是要尽量避免 synchronized
块在您指出的每个操作中都被命中。
all the threads running within the synchronized block will force a happen-before relation (i.e., there is no way threads will return null if the instance was properly set) Or am I wrong?
没错。你没有看错。
However, that can be simply because performance-wise it is the same since threads have to synchronized away, so why not be on the safe side and put the return inside?!.
没有理由不这样做,尽管我认为“安全方面”更多的是当其他人审查此代码并在将来担心它时引起恐慌,而不是从语言定义。同样,如果还有其他地方分配了 singleton
,那么 return
应该在 synchronized
块内。
考虑以下代码:
private static Singleton singleton;
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton; // <-- this part is important
}
这是来自 synchronized
块之外的 return singleton
。然而,其他一些(受人尊敬的)用户则持不同意见。
读完do we need volatile when implementing singleton using double-check locking后,我改变了主意。 (该问题的代码):
private static Singleton instance;
private static Object lock = new Object();
public static Singleton getInstance() {
if(instance == null) {
synchronized (lock) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
(众所周知,为什么第二个代码需要volatile
。)
然而,在再次查看这两个示例之后,我注意到第一个和第二个代码片段之间存在很大差异。在前者上,最外层的 if
在 synchronized
子句内,因此 synchronized
块内的所有线程 运行 将强制发生先行关系( 即, 如果实例设置正确,线程将无法 return null
)或者我错了吗?我希望执行以下操作顺序:
lock monitor
...
unlock monitor
...
read singleton
我注意到所有与第一个代码片段相似的在线示例在 synchronized
块内都有 return ;然而,这可能只是因为在性能方面它是相同的,因为线程必须同步,所以为什么不为了安全起见将 return 放在里面?! .
问题:
return 真的 需要在 synchronized
块内吗?读取 return 语句的单例值是否可以看到 synchronized
块开始之前的单例值?
编辑:我最初的回答是错误的,但我会保留它以显示我的错误所在。
singleton = new Singleton();
中的写入和return singleton
中的读取之间有一个program order
,它建立了所需的保证;即:这是安全的。
在这个:if (singleton == null)
和这个 singleton = new Singleton()
之间有 program order
关系,根据 JLS
,它也带来了 happens-before
顺序。
但是这个 write: singleton = new Singleton();
和这个 read : return singleton;
完全没有关系读起来 活泼 。 JLS
表示,如果出现此类阅读内容,则无法保证。因此,即使您将 new Singleton()
写入 singleton
,也无法保证 return singleton
会读取该写入的值;它仍然可以读取 null
。只有在 read 发生在同一个锁下时才有保证。
制作 singleton
volatile
解决了这个问题,因为您现在针对 singleton = new Singleton()
和 return singleton
创建了一个 synchronizes-with
订单,这隐含地创建了一个 happens-before
现在。
我是这样看的。
Does the return really needs to be inside the synchronized block?
否 return
不需要在 synchronized
块中 除非 可以将 singleton
字段分配到其他地方。但是,没有充分的理由说明 return
不应该在同步块内。如果整个方法都包含在同步中,那么如果我们在 Singleton
class 中,您只需将方法标记为同步即可。如果单例在其他地方被修改,这会更干净、更好。
就为什么它不需要在内部而言,因为您使用的是 synchronized
块,所以在块的开头有一个读屏障,在块的开头有一个写屏障结束,这意味着线程将获得 singleton
的最新值,并且只会分配一次。
读取内存屏障确保线程将看到更新的单例,该单例将是 null
或完全发布的对象。写内存屏障确保对 singleton
的任何更新都将写入主内存,其中包括 Singleton
的完整构造并将其发布到 singleton
字段。程序顺序保证在 synchronized
块中分配的 singleton
将作为相同的值返回,除非在另一个线程中对 singleton
进行了另一个分配,否则它将是未定义的。
如果您执行以下操作,程序顺序将更加有效。我倾向于在 singleton
为 volatile
时执行此操作(使用适当的双重检查锁定代码)。
synchronized (Singleton.class) {
Singleton value = singleton;
if (singleton == null) {
value = new Singleton();
singleton = value;
}
return value;
}
not thread-safe because of the return singleton outside the synchronized block
因为您使用的是 synchronized
块,所以这不是问题。双重检查锁定就是要尽量避免 synchronized
块在您指出的每个操作中都被命中。
all the threads running within the synchronized block will force a happen-before relation (i.e., there is no way threads will return null if the instance was properly set) Or am I wrong?
没错。你没有看错。
However, that can be simply because performance-wise it is the same since threads have to synchronized away, so why not be on the safe side and put the return inside?!.
没有理由不这样做,尽管我认为“安全方面”更多的是当其他人审查此代码并在将来担心它时引起恐慌,而不是从语言定义。同样,如果还有其他地方分配了 singleton
,那么 return
应该在 synchronized
块内。