我正在C#中构建一个多线程缓存,它将包含一个Car对象列表:
public static IList<Car> Cars {get; private set;}
Run Code Online (Sandbox Code Playgroud)
我想知道在没有锁定的情况下更改线程中的引用是否安全?
例如
private static void Loop()
{
while (true)
{
Cars = GetFreshListFromServer();
Thread.Sleep(SomeInterval);
}
}
Run Code Online (Sandbox Code Playgroud)
基本上,它归结为是否为汽车分配新的参考是原子的还是不是我想的.
如果不是,我显然必须为我的汽车使用私人领域,并锁定获取和设置.
阅读Joseph Albahari的线程教程,以下内容被提及为内存障碍的生成器:
lock陈述(Monitor.Enter/ Monitor.Exit)Interlocked班上的所有方法此外,Hans Passant和Brian Gideon 补充了以下内容(假设其中没有一个已经符合以前的类别之一):
Thread.Sleep()我想知道这个清单是否完整(如果完整清单甚至可以实际制作)
编辑补充建议:
假设我正在设计一个包装内部集合的线程安全类:
public class ThreadSafeQueue<T>
{
private readonly Queue<T> _queue = new Queue<T>();
public void Enqueue(T item)
{
lock (_queue)
{
_queue.Enqueue(item);
}
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
基于我的另一个问题,上面的实现是错误的,因为当它的初始化与其使用同时执行时可能会出现种族危险:
ThreadSafeQueue<int> tsqueue = null;
Parallel.Invoke(
() => tsqueue = new ThreadSafeQueue<int>(),
() => tsqueue?.Enqueue(5));
Run Code Online (Sandbox Code Playgroud)
上面的代码是可接受的非确定性的:该项目可能会或可能不会入队.但是,在当前的实现中,它也被破坏了,并且可能引起不可预测的行为,例如抛出IndexOutOfRangeException,NullReferenceException多次将相同的项目入队,或者陷入无限循环.这是因为Enqueue调用可能在将新实例分配给局部变量tsqueue之后但在内部_queue字段的初始化完成(或似乎完成)之前运行.
Per Jon Skeet:
在将对新对象的引用分配给实例之前,Java内存模型不能确保构造函数完成.Java内存模型经历了1.5版的重新加工,但是在没有volatile变量的情况下,双重检查锁定仍然被破坏(如在C#中).
可以通过向构造函数添加内存屏障来解决此种族危险:
public ThreadSafeQueue()
{
Thread.MemoryBarrier();
}
Run Code Online (Sandbox Code Playgroud)
同样,通过使字段变化可以更简洁地解决:
private volatile readonly Queue<T> _queue = new …Run Code Online (Sandbox Code Playgroud)