






















lock (lockObject) { // 要同步的代码块 }
在 C# 中,lock 关键字实际上是对 Monitor 类的一种简化用法,它会被编译器转换成 Monitor 类的方法调用。当你使用 lock 关键字时,编译器会自动创建一个 Monitor 对象,并在代码块的开始处调用 Monitor.Enter 方法获取锁,在代码块的结束处调用 Monitor.Exit 方法释放锁。
Monitor 类提供了细粒度的线程同步控制,它允许程序员手动管理同步块的进入和退出,并且支持更复杂的线程同步操作。
Monitor 类的基本用法如下:
// 进入同步代码块 Monitor.Enter(lockObject); try { // 同步代码块 } finally { // 退出同步代码块 Monitor.Exit(lockObject); }
分享一个简单的火车票购票系统的例子,来说明 lock 关键字的使用方式以及与Monitor 类的对比。
假设有一个火车票购票系统,多个用户同时在线购票。我们希望在多线程环境中正确管理火车票的分配,避免出现超卖或者座位重复分配的情况。
创建一个 TicketSystem 类来管理座位的状态和购票操作:
using System; using System.Threading; public class TicketSystem { private object lockObject = new object(); // 用于同步的锁对象 private int availableSeats; // 可用座位数量 public TicketSystem(int totalSeats) { availableSeats = totalSeats; } public void BuyTicket(string customerName, int numSeats) { lock (lockObject) { if (availableSeats >= numSeats) { // 模拟购票操作 Console.WriteLine($"{customerName} 购买了 {numSeats} 张票。"); availableSeats -= numSeats; } else { Console.WriteLine($"对不起,{customerName},没有足够的座位。"); } } } } class Program { static void Main(string[] args) { TicketSystem ticketSystem = new TicketSystem(15); // 总共15个座位 Thread[] threads = new Thread[20]; // 20个线程模拟20个用户 for (int i = 0; i < 20; i++) { threads[i] = new Thread(() => { string customerName = $"User {Thread.CurrentThread.ManagedThreadId}"; ticketSystem.BuyTicket(customerName, 1); }); } // 启动所有线程 foreach (var thread in threads) { thread.Start(); } // 等待所有线程执行完成 foreach (var thread in threads) { thread.Join(); } Console.WriteLine("所有用户购票结束。"); Console.ReadKey(); } }
using System; using System.Threading; public class TicketSystem { private object lockObject = new object(); // 用于同步的锁对象 private int availableSeats; // 可用座位数量 public TicketSystem(int totalSeats) { availableSeats = totalSeats; } public void BuyTicket(string customerName, int numSeats) { Monitor.Enter(lockObject); try { if (availableSeats >= numSeats) { // 模拟购票操作 Console.WriteLine($"{customerName} 购买了 {numSeats} 张票。"); availableSeats -= numSeats; } else { Console.WriteLine($"对不起,{customerName},没有足够的座位。"); } } finally { Monitor.Exit(lockObject); } } } class Program { static void Main(string[] args) { TicketSystem ticketSystem = new TicketSystem(15); // 总共15个座位 Thread[] threads = new Thread[20]; // 20个线程模拟20个用户 for (int i = 0; i < 20; i++) { threads[i] = new Thread(() => { string customerName = $"User {Thread.CurrentThread.ManagedThreadId}"; ticketSystem.BuyTicket(customerName, 1); }); } // 启动所有线程 foreach (var thread in threads) { thread.Start(); } // 等待所有线程执行完成 foreach (var thread in threads) { thread.Join(); } Console.WriteLine("所有用户购票结束。"); Console.ReadKey(); } }
上面的场景中使用lock和Moniter效果都是相同的,接下来描述lock 没有的功能。Monitor的Wait和Pulse 方法
我们知道春节火车票不是一次性放票的而是分段开放的,同时也有退票的情况。这将导致余票的数量动态变化。多个用户线程同时等待抢票,抢票系统一旦检测到有新的余票释放,将通知所有等待的线程进行抢票。这种情况下,我们需要使用线程同步机制确保多个线程之间的正确操作,并及时通知用户线程有新票可供抢购。
using System; using System.Threading; public class TicketSystem { private int availableTickets = 0; private object lockObject = new object(); public TicketSystem(int initialTickets) { this.availableTickets = initialTickets; } public void SellTicket() { Monitor.Enter(lockObject); try { while (availableTickets == 0) { Console.WriteLine($"{Thread.CurrentThread.Name} is waiting for available tickets..."); Monitor.Wait(lockObject); } // Simulate selling ticket availableTickets--; Console.WriteLine($"{Thread.CurrentThread.Name} sold a ticket. Available tickets: {availableTickets}"); } finally { Monitor.Exit(lockObject); } } public void AddTickets(int numTicketsToAdd) { Monitor.Enter(lockObject); try { // Simulate adding new tickets availableTickets += numTicketsToAdd; Console.WriteLine($"Added {numTicketsToAdd} new tickets. Available tickets: {availableTickets}"); // Notify waiting threads Monitor.PulseAll(lockObject); } finally { Monitor.Exit(lockObject); } } } public class Program { public static void Main(string[] args) { TicketSystem ticketSystem = new TicketSystem(0); // 初始没有票 // 创建并启动5个抢票线程 for (int i = 0; i < 5; i++) { Thread thread = new Thread(() => { while (true) { ticketSystem.SellTicket(); Thread.Sleep(1000); // 模拟抢票过程 } }); thread.Name = $"TicketBuyer {i + 1}"; thread.Start(); } // 模拟分段放票 for (int i = 0; i < 3; i++) { Thread.Sleep(3000); // 等待3秒模拟间隔放票 ticketSystem.AddTickets(5); // 每次放5张票 } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } }
这段代码实现了一个简单的抢票功能,具体功能和实现方式如下:
TicketSystem 类管理票务信息,包括余票数量和售票方法。Main 方法中创建多个线程模拟不同的抢票者,它们会不断尝试购买票。Monitor.Enter 和 Monitor.Exit 实现线程同步,确保多线程访问临界区时的安全。Monitor.Wait 和 Monitor.PulseAll 实现线程间的通信,唤醒等待的线程。由于 lock 示例本质上是实现了Monitor,所以也可以使用lock + Monitor.Wait 和 Monitor.PulseAll 方式使用。
public class TicketSystem { private int availableTickets = 0; public TicketSystem(int initialTickets) { this.availableTickets = initialTickets; } public void SellTicket() { lock (this) { while (availableTickets == 0) { Console.WriteLine($"{Thread.CurrentThread.Name} is waiting for available tickets..."); Monitor.Wait(this); } // Simulate selling ticket availableTickets--; Console.WriteLine($"{Thread.CurrentThread.Name} sold a ticket. Available tickets: {availableTickets}"); } } public void AddTickets(int numTicketsToAdd) { lock (this) { // Simulate adding new tickets availableTickets += numTicketsToAdd; Console.WriteLine($"Added {numTicketsToAdd} new tickets. Available tickets: {availableTickets}"); // Notify waiting threads Monitor.PulseAll(this); } } }
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。