lock
语句获取给定对象的互斥 lock,执行语句块,然后释放 lock。 持有 lock 时,持有 lock 的线程可以再次获取并释放 lock。 阻止任何其他线程获取 lock 并等待释放 lock。
lock
语句具有以下格式
lock (x) { // Your code... }
其中 x
是引用类型的表达式(任意的数据类型,包含:object、list、queue等,但string、bool等不能使用lock关键字)。 它完全等同于
object __lockObj = x; bool __lockWasTaken = false; try { System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken); // Your code... } finally { if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj); }
由于该代码使用 try...finally 块,即使在 lock
语句的正文中引发异常,也会释放 lock。
在 lock
语句的正文中不能使用 await 运算符。
当同步对共享资源的线程访问时,请锁定专用对象实例(例如,private readonly object balanceLock = new object();
)或另一个不太可能被代码无关部分用作 lock 对象的实例。 避免对不同的共享资源使用相同的 lock 对象实例,因为这可能导致死锁或锁争用。 具体而言,避免将以下对象用作 lock 对象:
this
(调用方可能将其用作 lock)。尽可能缩短持有锁的时间,以减少锁争用。
以下示例定义了一个 Account
类,该类通过锁定专用的 balanceLock
实例来同步对其专用 balance
字段的访问。 使用相同的实例进行锁定可确保尝试同时调用 Debit
或 Credit
方法的两个线程无法同时更新 balance
字段。
using System; using System.Threading.Tasks; public class Account { private readonly object balanceLock = new object(); private decimal balance; public Account(decimal initialBalance) => balance = initialBalance; public decimal Debit(decimal amount) { if (amount < 0) { throw new ArgumentOutOfRangeException(nameof(amount), "The debit amount cannot be negative."); } decimal appliedAmount = 0; lock (balanceLock) { if (balance >= amount) { balance -= amount; appliedAmount = amount; } } return appliedAmount; } public void Credit(decimal amount) { if (amount < 0) { throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative."); } lock (balanceLock) { balance += amount; } } public decimal GetBalance() { lock (balanceLock) { return balance; } } } class AccountTest { static async Task Main() { var account = new Account(1000); var tasks = new Task[100]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Run(() => Update(account)); } await Task.WhenAll(tasks); Console.WriteLine($"Account's balance is {account.GetBalance()}"); // Output: // Account's balance is 2000 } static void Update(Account account) { decimal[] amounts = { 0, 2, -3, 6, -2, -1, 8, -5, 11, -6 }; foreach (var amount in amounts) { if (amount >= 0) { account.Credit(amount); } else { account.Debit(Math.Abs(amount)); } } } }