本文介绍了在线程安全单例中,返回是否必须在同步块内的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下代码:

private static Singleton singleton;公共静态单例 get(){同步(Singleton.class){如果(单例==空){单例 = 新单例();}}返回单身人士;//<-- 这部分很重要}

这是来自这个问题的后续讨论.最初,我认为它是线程安全的.然而,一些可敬的用户认为这不是线程安全的,因为 return singletonsynchronized 块之外.然而,其他一些(受人尊敬的)用户则持相反意见.

在我阅读了在使用双重检查锁定实现单例时是否需要 volatile 之后,我改变了主意.(来自那个问题的代码):

 私有静态单例实例;私有静态对象锁=新对象();公共静态单例 getInstance() {如果(实例 == 空){同步(锁定){如果(实例 == 空){实例 = 新单例();}}}返回实例;}

(众所周知,为什么第二个代码需要volatile.)

然而,在再次查看这两个示例后,我注意到第一个和第二个代码片段之间存在很大差异.对于前者,最外面​​的 if 位于 synchronized 子句内,因此在 synchronized 块内运行的所有线程将强制发生先发生关系(ie, 如果实例设置正确,线程将不会返回 null )或者我错了?我希望以下操作顺序:

锁监视器...解锁监视器...读取单例

我注意到所有与第一个代码片段相似的在线示例都在 synchronized 块内返回;然而,这可能仅仅是因为在性能方面它是相同的,因为线程必须同步离开,所以为什么不安全起见并将返回值放在里面?!.

问题:

返回真的需要在synchronized块内吗?在 synchronized 块开始之前,读取 return 语句的单例值是否可以看到单例值?

解决方案

不,return 不需要在 synchronized 块中除非可以分配 singleton 字段别处.但是,return 不应该位于同步块内的理由并不充分.如果整个方法都包含在一个 synchronized 中,那么如果我们在此处的 Singleton 类中,您可以将该方法标记为 synchronized.如果单例在其他地方被修改,这会更干净、更好.

就为什么不需要在里面而言,因为您使用的是 synchronized 块,所以在块的开头有一个读屏障,在块的开头有一个写屏障最后,意味着线程将获得 singleton 的最新值,并且只会分配一次.

读取内存屏障确保线程将看到更新的单例,该单例要么是 null,要么是完全发布的对象.写内存屏障确保对 singleton 的任何更新都将写入主内存,其中包括 Singleton 的完整构造并将其发布到 singleton字段.程序顺序保证在 synchronized 块中分配的 singleton 将作为相同的值返回,除非在另一个线程中有另一个分配给 singleton 然后它将是未定义的.

如果您执行以下操作,程序顺序将更加有效.我倾向于在 singletonvolatile(使用适当的双重检查锁定代码)时这样做.

synchronized (Singleton.class) {单例值 = 单例值;如果(单例==空){值 = 新单例();单例 = 值;}返回值;}

不是线程安全的,因为在同步块之外返回单例

由于您使用的是 synchronized 块,所以这不是问题.双重检查锁定是为了避免 synchronized 块在您指出的每个操作中被命中.

在同步块中运行的所有线程都将强制发生先发生关系(即,如果实例设置正确,线程将无法返回 null)或者我错了吗?

没错.你没有看错.

然而,这可能仅仅是因为在性能方面它是相同的,因为线程必须同步离开,所以为什么不安全起见并将返回值放在里面呢?!

没有理由不这样做,尽管我会争辩说安全的一面"更多的是当其他人查看此代码并担心将来会引起恐慌,而不是更安全"时从语言定义的角度来看.同样,如果还有其他地方分配了 singleton,那么 return 应该在 synchronized 块内.

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. 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. (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.)

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 (i.e., there is no way threads will return null if the instance was properly set) Or am I wrong? 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; 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?!.

Question:

Does the return really need to be inside the synchronized block? Can the read of the singleton value for the return statement see a value of the singleton before the synchronized block start?

解决方案

No the return does not need to be in the synchronized block unless the singleton field can be assigned elsewhere. However, there is no good reason why the return shouldn't be inside of the synchronized block. 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. This would be cleaner and better in case singleton gets modified elsewhere.

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.

The read memory barrier ensures that the threads will see an updated singleton which will either be null or a fully published object. 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.

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).

synchronized (Singleton.class) {
    Singleton value = singleton;
    if (singleton == null) {
       value = new Singleton();
       singleton = value;
    }
    return value;
}

Since you are using a synchronized block, this isn't an issue. The double check locking is all about trying to avoid the synchronized block being hit on every operation as you point out.

That's correct. You aren't wrong.

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.

这篇关于在线程安全单例中,返回是否必须在同步块内的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-28 22:25