在使用线程安全时,我发现自己在执行锁定块中的代码之前总是“双重检查”,我想知道我是否做对了。考虑以下三种方法来做同样的事情:
示例 1:
private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
if(MyCollection[key] == null)
{
lock(locker)
{
MyCollection[key] = DoSomethingExpensive();
}
}
DoSomethingWithResult(MyCollection[key]);
}
示例 2:
private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
lock(locker)
{
if(MyCollection[key] == null)
{
MyCollection[key] = DoSomethingExpensive();
}
}
DoSomethingWithResult(MyCollection[key]);
}
示例 3:
private static SomeCollection MyCollection;
private static Object locker;
private void DoSomething(string key)
{
if(MyCollection[key] == null)
{
lock(locker)
{
if(MyCollection[key] == null)
{
MyCollection[key] = DoSomethingExpensive();
}
}
}
DoSomethingWithResult(MyCollection[key]);
}
我总是倾向于示例 3,这就是为什么我认为我在做正确的事情
- 线程 1 进入
DoSomething(string)
MyCollection[key] == null
所以线程 1 获得了锁,就像线程 2 进入一样MyCollection[key] == null
仍然为真,所以线程 2 等待获取锁- 线程 1 计算值
MyCollection[key]
并将其添加到集合中 - 线程 1 释放锁并调用
DoSomethingWithResult(MyCollection[key]);
- 线程 2 获得锁,此时
MyCollection[key] != null
- 线程 2 什么也不做,释放锁并继续其愉快的方式
示例 1 可行,但线程 2 可能会冗余计算MyCollection[key]
.
示例 2 可以工作,但每个线程都会获得一个锁,即使它不需要 - 这可能是一个(诚然非常小的)瓶颈。如果不需要,为什么要挂起线程?
我是否过度考虑了这一点,如果是这样,处理这些情况的首选方法是什么?