静态变量的线程安全初始化

我一直在使用这种模式来初始化我的类中的静态数据。 它看起来对我来说是安全的,但我知道细微的线程问题是多么微妙。 这是代码:

public class MyClass // bad code, do not use { static string _myResource = ""; static volatile bool _init = false; public MyClass() { if (_init == true) return; lock (_myResource) { if (_init == true) return; Thread.Sleep(3000); // some operation that takes a long time _myResource = "Hello World"; _init = true; } } public string MyResource { get { return _myResource; } } } 

这里有洞吗? 也许有一种更简单的方法可以做到这一点。

更新:共识似乎是静态构造函数是要走的路。 我使用静态构造函数提出了以下版本。

 public class MyClass { static MyClass() // a static constructor { Thread.Sleep(3000); // some operation that takes a long time _myResource = "Hello World"; } static string _myResource = null; public MyClass() { LocalString = "Act locally"; } // an instance constructor // use but don't modify public bool MyResourceReady { get { return _myResource != null; } } public string LocalString { get; set; } } 

我希望这更好。

您可以使用静态构造函数初始化静态变量,C#guarantee仅在每个AppDomain中调用一次。 不确定你是否考虑过它们。

所以你可以读到这个: http : //msdn.microsoft.com/en-us/library/aa645612 (VS.71) .aspx (静态构造函数)

而这: C#静态构造函数线程是否安全?

_myResource上执行lock()并在lock()语句中更改它似乎是一个坏主意。 考虑以下工作流程

  1. 线程1调用MyClass()
  2. 执行在行_init = true;之前停止_init = true; 在分配_myResource
  3. 处理器切换到线程2。
  4. 线程2调用MyClass() 。 由于_init仍为false_myResource ,因此它成功进入lock()语句块。
  5. _init仍为false ,因此线程2重新分配_myResource

解决方法:创建一个静态object并锁定此对象而不是初始化资源:

 private static readonly object _resourceLock = new object(); /*...*/ lock(_resourceLock) { /*...*/ } 

你的课不安全:

  1. 锁定后,您可以更改锁定的对象。
  2. 您有一个属性来获取资源而不锁定它。
  3. 你锁定一个原始类型 ,这通常不是一个好习惯。

这应该为你做:

 public class MyClass { static readonly object _sync = new object(); static string _myResource = ""; static volatile bool _init = false; public MyClass() { if (_init == true) return; lock (_sync) { if (_init == true) return; Thread.Sleep(3000); // some operation that takes a long time _myResource = "Hello World"; _init = true; } } public string MyResource { get { MyClass ret; // Correct lock(_sync) { ret = _myResource; } return ret; } } } 

更新:
正确,不应该直接返回静态资源…我已经相应地更正了我的示例。

根据您的使用情况(即,如果线程不需要使用此变量将信息相互传递),将成员变量标记为[ThreadStatic]可能是一种解决方案。
看到这里 。

 static string _myResource = ""; ... public MyClass() { ... lock (_myResource) { } } 

由于字符串实习 ,您不应该锁定字符串文字。 如果您锁定字符串文字并且多个类使用该字符串文字,那么您可能正在共享该锁定。 这可能会导致意外行为。