应用场景
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
lock语句根本使用的就是Monitor.Enter
和Monitor.Exit
,也就是说lock(this)时执行Monitor.Enter(this),大括号结束时执行Monitor.Exit(this)。
应用场景:经常会应用于防止多线程操作导致公用变量值出现不确定的异常,用于确保操作的安全性,比如抢购。
锁等于“行为可以预期”,不锁等于“行为不可预期”。
抢购举例
假设有商品库存为10,共有1000名顾客在几乎同一时间进行抢购。
不加lock的写法:
Parallel.For(0, customerCount, (i) =>
{
TryToBuyGoods(customerCount);
});
加lock的写法:(实际上转为单线程)
Parallel.For(0, customerCount, (i) =>
{
lock (inStockLock)
{
TryToBuyGoods(customerCount);
}
});
加Monitor的写法:(实际上转为单线程)
Parallel.For(0, customerCount, (i) =>
{
bool lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(inStockLock, ref lockWasTaken);
TryToBuyGoods(customerCount);
}
finally
{
if (lockWasTaken)
System.Threading.Monitor.Exit(inStockLock);
}
});
private void TryToBuyGoods(int customerCount)
{
var buyCustomer = Customers[random.Next(0, customerCount)];
var sleepTime = random.Next(1000, 10000);
if (inStock > 0)
{
//模拟购物业务逻辑处理时间(占用资源)
Thread.Sleep(sleepTime);
inStock--;
//上述流程是先处理一系列业务逻辑,后减库存,在不加lock的情况下会出现超卖现象
//建议的逻辑是,先减库存-处理业务逻辑-后续库存不足,恢复库存,并提示用户“购买失败”
Console.WriteLine($"顾客{buyCustomer.Name}购买了一件商品");
}
//Console.WriteLine($"当前库存:{inStock}");
}
结果如下:
可以看出,这种情况不加lock是非常危险的事情。