背景:用户领取优惠券,同一个用户需要加锁验证是否已经领取,不同用户则可以同时领取。
上代码示例:
1、创建Person类
/// <summary> /// Person类 /// </summary> public class Person { /// <summary> /// id /// </summary> public int Id { get; set; } /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 是否获得优惠券 /// </summary> public bool IsGetCoupon { get; set; } }
2.1、不加锁的方法(可能会出现重复领取的情况)
/// <summary> /// 获取优惠券 /// </summary> public static void GetCoupon(Person person) { Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},前来领取优惠券", DateTime.Now, person.Name); if (person.IsGetCoupon) { //假装业务处理 Thread.Sleep(1000); Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},已经领取,不可重复领取", DateTime.Now, person.Name); } else { //假装业务处理 Thread.Sleep(1000); //领取 person.IsGetCoupon = true; Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},领取成功", DateTime.Now, person.Name); } }
2.2、加lock锁的方法,所有来领优惠券的人,都得排对领(也不好)
/// <summary> /// Lock获取优惠券 /// </summary> public static void LockGetCoupon(Person person) { Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},前来领取优惠券", DateTime.Now, person.Name); lock (LockObj) { //判断是否已经领取 if (person.IsGetCoupon) { //假装业务处理 Thread.Sleep(1000); Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},已经领取,不可重复领取", DateTime.Now, person.Name); } else { //假装业务处理 Thread.Sleep(1000); //领取 person.IsGetCoupon = true; Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},领取成功", DateTime.Now, person.Name); } } }
2.3、mutex锁,互斥锁,只有相同id的人,才会排对领取,不同id的人就可以同时领取
/// <summary> /// Mutex,领取 /// </summary> /// <param name="person"></param> public static void MutexGetCoupon(Person person) { Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},前来领取优惠券", DateTime.Now, person.Name); using (var mutex = new Mutex(false, person.Id.ToString())) { try { if (mutex.WaitOne(-1, false)) { //判断是否已经领取 if (person.IsGetCoupon) { //假装业务处理 Thread.Sleep(1000); Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},已经领取,不可重复领取", DateTime.Now, person.Name); } else { //假装业务处理 Thread.Sleep(1000); //领取 person.IsGetCoupon = true; Console.WriteLine("date:{0:yyyy-MM-dd HH:mm:ss},name:{1},领取成功", DateTime.Now, person.Name); } } } catch (Exception ex) { //TxtLogHelper.WriteLog(ex); } finally { mutex.ReleaseMutex(); } } } }
3.1、开始测试(不加锁)
static void Main(string[] args) { //实例化三个人 Person p1 = new Person { Id = 24, Name = "Kobe" }; Person p2 = new Person { Id = 25, Name = "Rose" }; Person p3 = new Person { Id = 23, Name = "Lebl" }; //开启多线程、模拟三个人同时发起多次领取请求 for (int i = 0; i < 4; i++) { new Thread(() => { GetCoupon(p1); }).Start(); new Thread(() => { GetCoupon(p2); }).Start(); new Thread(() => { GetCoupon(p3); }).Start(); } Console.ReadLine(); }
测试结果:每个人都重复领取
3.2、测试lock锁方法,
private static readonly object LockObj = new object(); static void Main(string[] args) { //实例化三个人 Person p1 = new Person { Id = 24, Name = "Kobe" }; Person p2 = new Person { Id = 25, Name = "Rose" }; Person p3 = new Person { Id = 23, Name = "Lebl" }; //开启多线程、模拟三个人同时发起多次领取请求 for (int i = 0; i < 4; i++) { new Thread(() => { LockGetCoupon(p1); }).Start(); new Thread(() => { LockGetCoupon(p2); }).Start(); new Thread(() => { LockGetCoupon(p3); }).Start(); } Console.ReadLine(); }
测试结果:虽然避免了重复领取,但是每个人都的每个请求都要排对。如果用户量大的话,这种方式效率就太低了,所以不推荐。
3.3、测试mutex锁,互斥锁
static void Main(string[] args) { //实例化三个人 Person p1 = new Person { Id = 24, Name = "Kobe" }; Person p2 = new Person { Id = 25, Name = "Rose" }; Person p3 = new Person { Id = 23, Name = "Lebl" }; //开启多线程、模拟三个人同时发起多次领取请求 for (int i = 0; i < 4; i++) { new Thread(() => { MutexGetCoupon(p1); }).Start(); new Thread(() => { MutexGetCoupon(p2); }).Start(); new Thread(() => { MutexGetCoupon(p3); }).Start(); } Console.ReadLine(); }
测试结果:既避免了重复领取,也避免了堵塞用户请求的情况。见下面截图,Kobe、Rose、Lebl是同时领取的优惠券,但是每个人的重复请求都在排对
总结:mutex锁,完美的解决了此类问题。
--------------------------------------------华丽的分割线 --------------------------------------------
感谢各位大佬提出的问题和建议,我确实没有考虑到这些问题。