对象赋值线程安全吗?

本文关键字:安全 线程 赋值 对象 | 更新日期: 2023-09-27 18:10:31

这个线程安全吗?具体来说,GetMyObject()方法是否可能返回null?我理解这是可能的两个线程得到MyObject的不同实例,但我不关心。我只是想确保它是安全的假设GetMyObject()将永远不会返回null

   class Foo {
        private static MyObject obj;
        public static MyObject GetMyObject() {
            MyObject o = obj;
            if(o == null) {
                o = new MyObject();
                obj = o;
            }
            return o;
        }
        public static void ClearMyObject() {
            obj = null;
        }
    }
    class MyObject {}

对象赋值线程安全吗?

这个线程安全吗?

是否有可能为GetMyObject()方法返回null?

方法保证永远不会返回null。所有的读和写都保证是原子的。但是,线程不能保证读取静态字段obj的最新版本,并且线程不能保证对obj的更改顺序具有一致的视图。任意多个线程可能会竞争并观察到obj的不同值。我不认为这段代码是"线程安全的",但也许你对"线程安全"有不同的定义。这就是问这个问题的问题;

这个词没有一个人人都认同的标准定义。

GetMyObject()永远不会返回null。最简单的方法是注意到'o'是一个局部变量,因此没有其他人可以影响它。

好吧,让我们来推理一下:

public static MyObject GetMyObject() {
        MyObject o = obj;
        if(o == null) {
            o = new MyObject();
            obj = o;
        }
        return o;

}

只有一个return语句。该方法产生null返回值的唯一方式是,如果唯一的return语句return o在执行时o == nulltrue

如果return o执行时onull,这意味着我们从if块中取出o作为null。我们可以从o块中取出o作为null的唯一方法是,当if块的条件被测试时,如果o == nulltrue(如果o == nullfalse,那么o != null是true,并且由于o是一个局部变量,它不会受到任何其他线程的影响。但是o == nulltrue意味着我们最终进入了if块,现在当构造函数调用o = new MyObject()返回时,我们可以保证o不是nullif块中的第二个语句obj = o不会影响o的值。同样,由于o是一个局部变量,如果有多个线程在此代码路径中燃烧,则无关重要:每个线程都有自己的o,并且没有其他线程可以触摸任何其他线程的o

因此,无论o == nulltrue还是false,当if块完成时,我们最终得到o == nullfalse

因此,该方法保证返回一个非空值。

我只是想确保假设GetMyObject()永远不会返回null是安全的。

好吧,如果你只关心这个,那也没关系。但是让我们弄清楚一些事情。你的方法不是线程安全的。完全有可能构造两个MyObject实例,并且两个不同的调用者最终可能看到不同的返回值,即使您的意图很明显只有一个。为了解决这个问题,我建议使用Lazy<T>:

private static Lazy<MyObject> obj;
static Foo() {
    obj = new Lazy<MyObject>(
        () => new MyObject(),
        true
    );
}
public static MyObject GetMyObject() {
    return obj.Value;
}
public static void ClearMyObject() {
    obj = new Lazy<MyObject>(
        () => new MyObject(), 
        true
    );
}

它不会返回null,但根据大多数公认的定义,它绝不是线程安全的。假设您希望将对象存储为共享状态,并让其他线程访问该状态。在这种情况下,其他线程可能会创建自己的副本(如您所说)并尝试存储它们,但不能保证所有线程都能看到该对象的最新版本(或该对象的任何其他线程版本)。同样,你的ClearMyObject()方法也不会像你想象的那样。

使用Lazy<T>代替,它将提供你正在寻找的。

public static readonly Lazy<MyObject> myObject = new Lazy<MyObject>(() => new MyObject(), true);