您锁定的方式和内容取决于您在做什么。
假设您正在使用某种设备——比如咖啡机。您可能有一个如下所示的类:
public CoffeeMaker {
private IntPtr _coffeeHandle;
private Object _lock = new Object();
}
在这种情况下,您正在保护对 _coffeeHandle 的访问——一个指向真实物理设备的指针/句柄,所以这很容易:
public int AvailableCups {
get {
lock (_lock) {
return GetAvailableCups(_coffeeHandle); // P/Invoked
}
}
}
public void Dispense(int nCups)
{
lock (_lock) {
int nAvail = GetAvailableCups(_coffeeHandle);
if (nAvail < nCups) throw new CoffeeException("not enough coffee.");
Dispense(_coffeeHandle, nCups); // P/Invoked
}
}
因此,如果我正在运行一个多线程应用程序,我(可能)不想在分配时读取可用的杯子数量(可能是硬件错误)。通过保护对句柄的访问,我可以确保这一点。此外,不能在我已经分配时要求我分配 - 那会很糟糕,所以这也受到保护。最后,除非我有足够的咖啡可用,并且您注意到我没有使用我的公共财产来检查,否则我不会分配 - 这样确保有足够的咖啡和分配的操作是联系在一起的。神奇的词是原子的——它们不能被分开而不产生问题。
如果您有一个且只有一个需要保护的资源实例,则可以使用静态对象作为锁。想一想,“我有单身人士吗?” 这将是您何时可能需要静态锁的指南。例如,假设 CoffeeMaker 有一个私有构造函数。相反,您有一个构造咖啡机的工厂方法:
static Object _factLock = new Object();
private CoffeeMaker(IntPtr handle) { _coffeeHandle = handle; }
public static CoffeeMaker GetCoffeeMaker()
{
lock (_factLock) {
IntPtr _handle = GetCoffeeMakerHandle(); // P/Invoked
if (_handle == IntPtr.Zero) return null;
return new CoffeeMaker(_handle);
}
}
现在在这种情况下,感觉 CoffeeMaker 应该实现 IDisposable以便处理句柄,因为如果你不释放它,那么有人可能不会得到他们的咖啡。
不过也有一些问题——如果咖啡不够,我们应该做更多——这需要很长时间。见鬼 - 分配咖啡需要很长时间,这就是为什么我们要小心保护我们的资源。现在你在想,真的所有这些咖啡机的东西都应该在它自己的线程中,并且应该有一个事件在咖啡完成后被触发,然后它开始变得复杂,你明白知道的重要性你要锁定什么以及何时锁定,这样你就不会因为你问有多少杯而阻止制作咖啡。
如果“死锁”、“原子”、“监视器”、“等待”和“脉冲”这些词对你来说听起来都很陌生,你应该考虑阅读多处理/多线程的一般知识,看看你是否能解决公平的理发店问题或哲学家就餐问题,都是资源争用的典型例子。