C# 在使用setter时,何时应该在多线程单例中锁定静态实例?
试图了解何时锁定静态变量被认为是最佳实践。静态实例设置程序是线程安全的吗?如果不是,应该是吗?为什么不保证线程安全会导致什么后果C# 在使用setter时,何时应该在多线程单例中锁定静态实例?,c#,multithreading,thread-safety,C#,Multithreading,Thread Safety,试图了解何时锁定静态变量被认为是最佳实践。静态实例设置程序是线程安全的吗?如果不是,应该是吗?为什么不保证线程安全会导致什么后果 class MyClass { private static MyClass _instance; private static readonly object _padlock = new object(); public static MyClass Instance { get {
class MyClass
{
private static MyClass _instance;
private static readonly object _padlock = new object();
public static MyClass Instance
{
get
{
if(_instance == null)
{
lock(_padlock)
{
if(_instance == null)
{
_instance = new MyClass();
}
}
}
return _instance;
}
set => _instance = value;
}
}
这称为双重检查锁定
但是,双重检查锁定要求基础字段为1
简言之,分配是原子的,但需要通过跨不同内核/CPU的锁进行完全同步。另一个并发读取该值的内核可能会得到一个过时的值cached1
有几种方法可以使代码线程安全:
避免双重检查锁定,只需执行lock语句中的所有操作。
使用volatile关键字将字段设置为volatile。
使用保证线程安全的Lazy类。
注:完全不设防的二传手进一步增加了复杂性3
然而,在您的情况下,使用双重检查锁定可能会与单个检查和volatile字段锁定一起工作,但我认为您最好的选择是完全锁定所有内容并确保安全
public static MyClass Instance
{
get
{
lock(_padlock)
{
if(_instance == null)
_instance = new MyClass();
return _instance;
}
}
set
{
lock(_padlock)
{
_instance = value;
}
}
}
注意:是的,它将招致性能惩罚
参考文献
一,
二,
3评论来自
额外资源
这称为双重检查锁定
但是,双重检查锁定要求基础字段为1
简言之,分配是原子的,但需要通过跨不同内核/CPU的锁进行完全同步。另一个并发读取该值的内核可能会得到一个过时的值cached1
有几种方法可以使代码线程安全:
避免双重检查锁定,只需执行lock语句中的所有操作。
使用volatile关键字将字段设置为volatile。
使用保证线程安全的Lazy类。
注:完全不设防的二传手进一步增加了复杂性3
然而,在您的情况下,使用双重检查锁定可能会与单个检查和volatile字段锁定一起工作,但我认为您最好的选择是完全锁定所有内容并确保安全
public static MyClass Instance
{
get
{
lock(_padlock)
{
if(_instance == null)
_instance = new MyClass();
return _instance;
}
}
set
{
lock(_padlock)
{
_instance = value;
}
}
}
注意:是的,它将招致性能惩罚
参考文献
一,
二,
3评论来自
额外资源
在我看来,在设定器上锁定或不锁定,你总是会遇到时间问题。想象一下这些场景: 您在setter上有一个锁,但是在该锁启用之前,调用getter。调用方获取旧实例。 你在setter上有一个锁,但是在锁被锁定之后,对getter的调用就进来了。调用方等待锁释放,然后获取新实例。 您没有对setter的锁定,并且调用在您替换实例之前进入。调用方获取旧实例。 您没有对setter的锁定,并且在您替换实例之后,调用将进入。调用方获取新实例。 有锁和无锁,调用方接收哪个实例是一个时间问题 我能看到的唯一问题是,您是否希望能够将Instance设置为null。如果是这种情况,您当前的代码将无法工作,因为在If语句和返回它之间可能会更改_实例。您可以通过复制引用来解决此问题:
public static MyClass Instance
{
get
{
var instanceSafeRef = _instance;
if(instanceSafeRef == null)
{
lock(_padlock)
{
if(_instance == null)
{
_instance = new MyClass();
}
instanceSafeRef = _instance;
}
}
return instanceSafeRef;
}
set => _instance = value;
}
在我看来,在设定器上锁定或不锁定,你总是会遇到时间问题。想象一下这些场景: 您在setter上有一个锁,但是在该锁启用之前,调用getter。调用方获取旧实例。 你在setter上有一个锁,但是在锁被锁定之后,对getter的调用就进来了。调用方等待锁释放,然后获取新实例。 您没有对setter的锁定,并且调用在您替换实例之前进入。调用方获取旧实例。 您没有对setter的锁定,并且在您替换实例之后,调用将进入。调用方获取新实例。 有锁和无锁,调用方接收哪个实例是一个时间问题 我能看到的唯一问题是,您是否希望能够将Instance设置为null。如果是这种情况,您当前的代码将无法工作,因为在If语句和返回它之间可能会更改_实例。您可以通过复制引用来解决此问题:
public static MyClass Instance
{
get
{
var instanceSafeRef = _instance;
if(instanceSafeRef == null)
{
lock(_padlock)
{
if(_instance == null)
{
_instance = new MyClass();
}
instanceSafeRef = _instance;
}
}
return instanceSafeRef;
}
set => _instance = value;
}
你为什么有二传手?这样你就可以在某个时候用一个新的类替换MyClass了吗?正确。它将被用作环境上下文模式的一部分,我将在其中实现一个工厂类。我看不出锁定它的理由,但它似乎也不是线程安全的。我建议将setter隐藏在单独的、适当命名的方法后面,以阻止随机分配,因为可能会发生这种情况。。也不,它不是线程安全的。我同意
它不是线程安全的,但是任务是原子的,这让我很困惑。谢谢大家的帮助。你为什么有二传手?这样你就可以在某个时候用一个新的类替换MyClass了吗?正确。它将被用作环境上下文模式的一部分,我将在其中实现一个工厂类。我看不出锁定它的理由,但它似乎也不是线程安全的。我建议将setter隐藏在单独的、适当命名的方法后面,以阻止随机分配,因为可能会发生这种情况。。同样不,它不是线程安全的。我同意它不是线程安全的,但是原子的任务让我有点困惑。谢谢大家的帮助。我知道双重检查锁定,更喜欢使用Lazy,但这是一种罕见的需要setter的情况。在过去,我使用volatile关键字,但以int为例。所以您建议锁定setter,使其线程安全?@ChrisGessler是的,您还需要锁定setter。@MichaelRandall-您能解释一下不锁定它的负面影响吗?例如,我的内存是否会损坏?它会使exe崩溃,还是我只是接收对象的缓存旧副本?我尝试用数千个线程来破坏应用程序,但它不能。@ChrisGessler不,分配是原子的,最坏的情况是你最终得到了错误的引用,你认为你得到了默认值,但这是默认值的不同实例化,或者你认为你已经设置了默认值,但你最终得到了默认值,还是邪恶versa@MichaelRandall-我同意。安全第一!谢谢你的帮助!谢谢我知道双重检查锁定,更喜欢使用Lazy,但这是一种罕见的需要setter的情况。在过去,我使用volatile关键字,但以int为例。所以您建议锁定setter,使其线程安全?@ChrisGessler是的,您还需要锁定setter。@MichaelRandall-您能解释一下不锁定它的负面影响吗?例如,我的内存是否会损坏?它会使exe崩溃,还是我只是接收对象的缓存旧副本?我尝试用数千个线程来破坏应用程序,但它不能。@ChrisGessler不,分配是原子的,最坏的情况是你最终得到了错误的引用,你认为你得到了默认值,但这是默认值的不同实例化,或者你认为你已经设置了默认值,但你最终得到了默认值,还是邪恶versa@MichaelRandall-我同意。安全第一!谢谢你的帮助!