C#静态类私有字段线程安全吗?

时间:2018-12-20 13:38:56

标签: c# multithreading class static private

我有一个从多个线程访问的C#静态类。两个问题:

  1. 在声明时初始化字段时,我的私有静态字段线程安全吗?
  2. 从静态构造函数中创建私有静态字段时应该锁定吗?

来自不同线程的静态类的使用:

class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                Task.Run(() =>
                {
                    string name = MyStaticClass.GetValue(9555);
                    //...
                });
            }
        }
}

静态类的选项1:

public static class MyStaticClass
    {
        private static MyClass _myClass = new MyClass();

        public static string GetValue(int key)
        {
            return _myClass.GetValue(key);
        }
    }

静态类的选项2:

public static class MyStaticClass
    {
        private static MyClass _myClass;
        private static object _lockObj = new object();

        static MyStaticClass()
        {
            InitMyClass();
        }

        private static void InitMyClass()
        {
            if (_myClass == null)
            {
                lock(_lockObj)
                {
                    if (_myClass == null)
                    {
                        _myClass = new MyClass();
                    }
                }
            }
        }

        public static string GetValue(int key)
        {
            return _myClass.GetValue(key);
        }
    }

从静态类创建的实例类:

public class MyClass
    {
        private Dictionary<int, Guid> _valuesDict = new Dictionary<int, Guid>();

        public MyClass()
        {
            for (int i = 0; i < 10000; i++)
            {
                _valuesDict.Add(i, Guid.NewGuid());
            }
        }

        public string GetValue(int key)
        {
            if (_valuesDict.TryGetValue(key, out Guid value))
            {
                return value.ToString();
            }

            return string.Empty;
        }
    }

4 个答案:

答案 0 :(得分:13)

  

从静态构造函数中初始化私有静态字段时应该锁定吗?

我们不要在这里埋葬壁垒:

永远不要锁定静态构造函数。静态构造函数已经被框架锁定,因此它们只能在一个线程上运行一次。

这是一个特殊情况,其中包含一些更好的建议:绝对不要在静态构造函数中对线程做任何事情。静态构造函数被有效锁定,并且该锁定可以被访问您的类型的任何代码所争辩的事实,这意味着您可以非常迅速地陷入意想不到且很难看到的死锁。我在这里举一个例子:https://ericlippert.com/2013/01/31/the-no-lock-deadlock/

如果要进行延迟初始化,请使用Lazy<T>构造;它是由知道如何使其安全的专家撰写的。

  

在声明时初始化字段时,我的私有静态字段线程安全吗?

当从多个线程中调用程序元素时,线程安全性是保留程序不变式。您尚未说出什么是不变式,因此无法说出程序是否“安全”。

如果您担心的不变量是在执行第一个静态方法或创建第一个实例类型之前观察到静态构造函数正在运行,则C#会保证这样做。当然,如果您在静态构造函数中编写疯狂的代码,则可能会发生疯狂的事情,因此,再次尝试使静态构造函数保持非常简单。

答案 1 :(得分:1)

静态类的

字段默认情况下不是线程安全的,应该避免使用,除非只是出于读取目的。 不利的一面也是“锁定”,它将在多线程环境中创建序列化处理。

public static class MyStaticClass
{
    private static MyClass _myClass;
    private static object _lockObj;

    static MyStaticClass()
    {
           _myClass = new MyClass();
           _lockObj = new object();
    }

    public static string GetValue(int key)
    {
        return _myClass.GetValue(key);
    }
    public static void SetValue(int key)
    {
        lock(_lockObj)
        {           
             _myClass.SetValue(key);
        }
    }
}

答案 2 :(得分:0)

您的第二个版本更可取。您可以通过将字段设置为readonly来将其锁定得更多:

public static class MyStaticClass
{
    private static readonly MyClass _myClass = new MyClass();

    public static string GetValue(int key)
    {
        return _myClass.GetValue(key);
    }
}

您的意图似乎是最初将_myClass设置为MyClass的一个实例,而从未将其设置为另一个。 readonly通过指定只能在静态构造函数中进行设置或如上所述进行初始化来设置一次。不仅其他线程无法设置它,而且任何更改它的尝试都将导致编译器错误。

您可以省略readonly,而再也不必设置_myClass,但是readonly既可以传达信息又可以实现您的意图。

这是棘手的地方:您对MyClass实例的引用是线程安全的。您不必担心各种线程是否会将其替换为其他实例(或将其设置为null),并且将在所有线程尝试与之交互之前将其实例化。

这不是 的作用是使MyClass线程安全。不知道它的作用或与它的交互方式,我无法说出需求或关注的是什么。

如果这是一个问题,一种方法是使用lock来防止不应发生的并发访问,这与@ Mahi1722完全相同。我包括了该答案的代码(不要not窃,但如果该答案发生任何事情,那么此答案将指代不存在的答案。)

public static class MyStaticClass
{
    private static MyClass _myClass = new MyClass();
    private static object _lockObj = new object();

    public static string GetValue(int key)
    {
        return _myClass.GetValue(key);
    }

    public static void SetValue(int key)
    {
        lock(_lockObj)
        {           
             _myClass.SetValue(key);
        }
    }
}

_myClass进行交互的两个方法都使用_lockObject进行锁定,这意味着当另一个线程正在执行任一方法时,任何一个方法的执行都会被阻塞。

这是一种有效的方法。另一个方法是通过使用并发集合或在该类中实现此类锁来实际上使MyClass线程安全。这样,您不必在每个使用lock实例的类中都使用MyClass语句。您可以在知道它是内部管理的情况下使用它。

答案 3 :(得分:-1)

都是正确的, 但无需lock内的static constructor。 所以,我将选择第一个选项,它更短,更清晰