[英]In a Thread Safe Singleton does the return have to be inside the synchronized block
Consider the following code:考虑以下代码:
private static Singleton singleton;
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton; // <-- this part is important
}
This comes as a follow-up discussion from this question .这是这个问题的后续讨论。 Initially, I thought that it was thread-safe.
最初,我认为它是线程安全的。 However, some respectable users argue that is not thread-safe because of the
return singleton
outside the synchronized
block.然而,一些受人尊敬的用户认为这不是线程安全的,因为在
synchronized
块之外return singleton
。 Some other also (respectable) users, however, argued otherwise.然而,其他一些(受人尊敬的)用户则持不同观点。
After I have read do we need volatile when implementing singleton using double-check locking , I changed my mind.在我阅读了使用双重检查锁定实现 singleton 时是否需要 volatile之后,我改变了主意。 (The code from that question):
(该问题的代码):
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;
}
(It is well-known why the volatile
is needed on the second code.) (众所周知,为什么第二个代码需要
volatile
。)
However, after looking again at both examples, I have noticed that there is a big difference between the first and the second code snippets.但是,在再次查看这两个示例后,我注意到第一个和第二个代码片段之间存在很大差异。 On the former the outermost
if
is inside the synchronized
clause therefore all the threads running within the synchronized
block will force a happen-before relation ( ie, there is no way threads will return null
if the instance was properly set) Or am I wrong?在前者中,最外面的
if
位于synchronized
子句内,因此在synchronized
块中运行的所有线程都将强制发生前发生关系(即,如果正确设置了实例,线程将无法返回null
)还是我错了? I would expect the following order of actions:我希望采取以下行动顺序:
lock monitor
...
unlock monitor
...
read singleton
I have noticed that all the examples online that are similar to the first code snippet have the return inside the synchronized
block;我注意到所有与第一个代码片段相似的在线示例都在
synchronized
块内返回; 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?!然而,这可能仅仅是因为在性能方面它是相同的,因为线程必须同步,所以为什么不安全起见,把 return 放在里面呢?! .
.
Question:问题:
Does the return really need to be inside the synchronized
block? return真的需要在
synchronized
块内吗? Can the read of the singleton value for the return statement see a value of the singleton before the synchronized
block start?读取 singleton 值的返回语句是否可以在
synchronized
块开始之前看到 singleton 的值?
Does the return really needs to be inside the synchronized block?
return 真的需要在同步块内吗?
No the return
does not need to be in the synchronized
block unless the singleton
field can be assigned elsewhere.不,
return
不需要在synchronized
块中,除非singleton
字段可以分配到其他地方。 However, there is no good reason why the return
shouldn't be inside of the synchronized block.但是,没有充分的理由说明
return
不应在同步块内。 If the entire method is wrapped in a synchronized then you can just mark the method as synchronized if we are in the Singleton
class here.如果整个方法都包含在同步中,那么如果我们在
Singleton
class 中,您可以将方法标记为同步。 This would be cleaner and better in case singleton gets modified elsewhere.如果 singleton 在其他地方被修改,这将更清洁和更好。
In terms of why it doesn't need to be inside, since you are using a synchronized
block, there is a read-barrier crossed at the start of the block and a write-barrier at the end, meaning that the threads will get the most up-to-date value of singleton
and it will only be assigned once.至于为什么它不需要在里面,因为你使用的是
synchronized
块,在块的开头有一个读屏障,在最后有一个写屏障,这意味着线程将获得singleton
的最新值,它只会被分配一次。
The read memory barrier ensures that the threads will see an updated singleton which will either be null
or a fully published object.读取 memory 屏障确保线程将看到更新的 singleton ,它将是
null
或完全发布的 ZA8CFDE6331BAC49EB26 The write memory barrier ensures that any updates to singleton
will be written to main memory which includes the full construction of Singleton
and the publishing of it to the singleton
field. The write memory barrier ensures that any updates to
singleton
will be written to main memory which includes the full construction of Singleton
and the publishing of it to the singleton
field. Program order guarantees that the singleton
assigned within the synchronized
block will be returned as the same value unless there is another assignment in another thread to singleton
then it will be undefined.程序顺序保证在
synchronized
块中分配的singleton
将作为相同的值返回,除非另一个线程中有另一个分配给singleton
,否则它将是未定义的。
Program order would be more in force if you did something like the following.如果您执行以下操作,程序顺序将更加有效。 I tend to do this when
singleton
is volatile
(with appropriate double-check locking code).当
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
不是线程安全的,因为在同步块之外返回 singleton
Since you are using a synchronized
block, this isn't an issue.由于您使用的是
synchronized
块,因此这不是问题。 The double check locking is all about trying to avoid the synchronized
block being hit on every operation as you point out.正如您所指出的,双重检查锁定就是要避免在每个操作上都遇到
synchronized
块。
all the threads running within the synchronized block will force a happen-before relation (ie, there is no way threads will return null if the instance was properly set) Or am I wrong?
在同步块中运行的所有线程都将强制发生先发生关系(即,如果正确设置了实例,线程将无法返回 null)还是我错了?
That's correct.这是正确的。 You aren't 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?..
然而,这可能仅仅是因为在性能方面它是相同的,因为线程必须同步,所以为什么不安全起见并将返回值放在里面呢?
No reason not to although I would argue that the "safe side" is more about causing consternation when others review this code and are worrying about it in the future, as opposed to being "safer" from the standpoint of the language definition.没有理由不这样做,尽管我认为“安全方面”更多的是在其他人查看此代码并在将来担心它时引起惊愕,而不是从语言定义的角度来看“更安全”。 Again, if there are other places where
singleton
is assigned then the return
should be inside of the synchronized
block.同样,如果在其他地方分配了
singleton
,则return
应该在synchronized
块的内部。
EDIT: I was wrong in the initial answer , but I will keep it to show where my mistake was.编辑:我在最初的答案中错了,但我会保留它以显示我的错误在哪里。
there is a program order
between the write in the singleton = new Singleton();
program order
singleton = new Singleton();
and the read in return singleton
, which establishes the needed guarantees;和读取
return singleton
,它建立了所需的保证; ie: this is safe.即:这是安全的。
between this: if (singleton == null)
and this singleton = new Singleton()
there is program order
relationship, which according to the JLS
, brings also a happens-before
order.在这之间:
if (singleton == null)
和这个singleton = new Singleton()
存在program order
关系,根据JLS
,这也带来了happens-before
顺序。
But this write : singleton = new Singleton();
但是这样写:
singleton = new Singleton();
has no relationship at all with this read : return singleton;
与此阅读完全没有关系:
return singleton;
, which is a racy read. ,这是一本活泼的读物。
JLS
says that in case of such racy reads, nothing is guaranteed. JLS
表示,如果出现这种不正当的读取,则无法保证。 So even if you wrote new Singleton()
to singleton
, there is no guarantee that return singleton
will read that written value;因此,即使您将
new Singleton()
写入singleton
,也不能保证return singleton
会读取该写入值; it can still read null
.它仍然可以读取
null
。 The guarantee is only there when reading happens under the same lock.只有在同一个锁下进行读取时,才会有保证。
Making singleton
volatile
fixes that problem, because you now create a synchronizes-with
order against: singleton = new Singleton()
and return singleton
, which implicitly creates a happens-before
now.使
singleton
volatile
解决了这个问题,因为您现在创建了一个synchronizes-with
订单: singleton = new Singleton()
并return singleton
happens-before
现在隐式创建。
This is how I see it.这就是我的看法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.