【问题标题】:Make singleton thread safe by using private static methods?通过使用私有静态方法使单例线程安全?
【发布时间】:2011-07-29 21:56:17
【问题描述】:

我有一个类,我想通过创建它的静态实例将其用作单例。当然,我也希望它是线程安全的。

假设我没有共享任何私人数据。但是如果我没记错的话,仍然存在调用静态对象实例的方法时,方法中的变量是跨线程共享的,并且会产生不可预知的结果的问题。

但是,当调用真正的静态方法时,会创建一个新的堆栈帧,因此无论如何它都是线程安全的(对自身而言)。再说一次,如果我没记错的话。

单例的这种模式是线程安全的吗?

class Singleton
{
    public object SomeMethod(object arg) {
        return Singleton.SomeMethodImpl(arg);

    }
    private static object SomeMethodImpl(object arg) {
        // is in unique stack frame?
        object Result;
        ...
        return Result;
    }
}

如果您想知道为什么我不首先创建一个静态类 - 我需要一个基于接口的单例,并且具有不同的实现,作为策略模式的一部分。这不适用于静态类。

【问题讨论】:

  • 如果你想要一个关于如何创建单例的好例子,我会看看:dofactory.com/Patterns/PatternSingleton.aspx
  • 刚刚略读。好信息 - 已添加书签。就我现在的目的而言,我认为我不需要担心锁定,因为我没有访问类中的任何共享数据。

标签: c# thread-safety singleton static-methods


【解决方案1】:

只要您的方法没有从实例方法或全局范围变量中获取状态,您的方法就是可重入且线程安全的。它不一定是静态的。所以像:

int AddTwo(int a, int b)
{
  return a + b;
}

这完全是线程安全的,可以随心所欲地调用。即使在方法内部定义变量也可以,只要它们是方法之间共享的实例变量。

方法如:

string ReverseString(string s)
{
    char[] charArray = s.ToCharArray();
    Array.Reverse( charArray );
    return new string( charArray );
}

上面的方法也是可重入和线程安全的。

一旦您开始添加变量,无论它们是静态变量还是来自不同范围的实例,您就会开始遇到线程安全问题。

class BadExample
{
    private int counter;

    private void IncrementCounter()
    {
        ++counter;
    }
}

在上面的例子中,IncrementCounter() 方法不是线程安全的。

【讨论】:

  • 但是如果我在非静态方法中定义任何局部变量,它就会变得非线程安全,对吧?这是我试图解决的问题 - 我必须做一些事情,比如从需要私有临时存储的缓存中提取东西。
  • 问题不是局部变量。它的内容来自哪里。在上面的示例中,所有“状态”都来自方法的参数。在您的情况下,这取决于您的缓存是否是线程安全的。它是如何写入的?它是如何读取的?
  • 缓存很好,它是ConcurrentDictionaryHttpContext.Cache。但我的印象是,在静态实例化类的非静态方法中定义局部变量会给你带来麻烦 b/c 两个线程可以同时调用该方法并共享该局部变量的同一个实例.
  • 关于您的编辑:所以,基本上,如果我在类中没有定义任何字段(例如,除了来自外部、线程安全的源或作为参数提供给方法的数据之外,不使用任何数据),我可以创建它的静态实例并且它将是线程安全的吗?我最初的前提是错误的——调用类的静态实例的非静态成员仍会为其私有变量创建一个新的堆栈框架?
  • 在该函数范围内创建的所有内容都在该函数的调用堆栈中。因此,除非您从其他地方获取变量,否则没有问题。
【解决方案2】:

如果我明白你的意思,那么你是对的。

object Result;   // this is on its unique stack frame and is safe so far
Result = new ... // creating something on the heap that Result points to
                 // still safe because it's the only reference to it

即使多个线程调用它,它们也会在堆上创建不同的新变量并将它们分配给不同堆栈上的 Result。

如果你有私有字段,你会面临的唯一危险。

方法中的变量是临时的,仅对该方法调用可见。稍后或并发的方法调用分别重新创建这些变量。

您唯一关心的是静态或实例字段。这些需要同步。

【讨论】:

    【解决方案3】:

    由于您指定的原因,您上面的代码是线程安全的。我看到的问题是您没有实现单例。

    您主要担心线程安全吗?如果是这样,线程安全通常适用于在线程之间共享的对象实例。这意味着只要您不跨线程共享普通对象或在类级别创建静态数据就可以了。

    我正在添加一个使用带接口的单例的示例,无论有无工厂。注意:我没有运行此代码。

    public interface ISomething
    {
        void Method();
    }
    
    public class Class1 : ISomething
    {
        public void Method()
        {
            throw new NotImplementedException();
        }
    }
    
    public class Class2 : ISomething
    {
        public void Method()
        {
            throw new NotImplementedException();
        }
    }
    
    public class Singleton
    {
        private static ISomething privateObject;
    
        public static ISomething Instance()
        {
            lock (privateObject)
            {
                if (privateObject == null)
                {
    
                    privateObject = new Class1();
                }
            }
    
            return privateObject;
        }
    }
    
    public class SingletonUsingFactory
    {
        private static ISomething privateObject;
    
        public static ISomething Instance(int param)
        {
            lock (privateObject)
            {
                if (privateObject == null)
                {
                    privateObject = FactoryClass.CreationObject(param);
                }
            }
    
            return privateObject;
        }
    }
    
    public static class FactoryClass
    {
        public static ISomething CreationObject(int whatToCreate)
        {
            ISomething createdObject;
    
            switch (whatToCreate)
            {
                case 0:
                    createdObject = new Class1();
                    break;
                case 1:
                    createdObject = new Class2();
                    break;
                default:
                    throw new Exception();
            }
    
            return createdObject;
        }
    }
    

    【讨论】:

    • 这个想法是我可以在另一个静态类中将其创建为单例,例如public static Singleton MySingleton = new Singletion()
    • 您可以使用单例模式让您的接口独立于实现。创建单个对象时只需使用锁
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多