“线程同步”的含义

 

当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchronization)”。

线程同步的道理虽然简单,但却是给多线程开发带来复杂性的根源之一。当线程同步不好时,有可能会出现一种特殊的情形——死锁(Dead Lock)

“死锁”的含义

 

死锁表示系统进入了一个僵化状态,所有线程都没有执行完毕,但却谁也没法继续执行。究其根源,是因为“进程推进顺序不当”和“资源共享”。如例:

1)进程推进顺序不当造成死锁

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace JoinLeadToDeadlock
  6. {
  7. class Program
  8. {
  9. static Thread mainThread;
  10. static void Main(string[] args)
  11. {
  12. Console.WriteLine("主线程开始运行");
  13. mainThread = Thread.CurrentThread;
  14. Thread ta = new Thread(new ThreadStart(ThreadAMethod));
  15. ta.Start();  //线程A开始执行
  16. Console.WriteLine("主线程等待线程A结束……");
  17. ta.Join();    //等待线程A结束
  18. Console.WriteLine("主线程退出");
  19. }
  20. static void ThreadAMethod()
  21. {
  22. for (int i = 0; i < 10; i++)
  23. {
  24. Console.WriteLine(Convert.ToString(i) + ": 线程A正在执行");
  25. Thread.Sleep(1000);
  26. }
  27. Console.WriteLine("线程A等待主线程退出……");
  28. mainThread.Join();  //等待主线程结束
  29. }
  30. }
  31. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace JoinLeadToDeadlock
  6. {
  7. class Program
  8. {
  9. static Thread mainThread;
  10. static void Main(string[] args)
  11. {
  12. Console.WriteLine("主线程开始运行");
  13. mainThread = Thread.CurrentThread;
  14. Thread ta = new Thread(new ThreadStart(ThreadAMethod));
  15. ta.Start();  //线程A开始执行
  16. Console.WriteLine("主线程等待线程A结束……");
  17. ta.Join();    //等待线程A结束
  18. Console.WriteLine("主线程退出");
  19. }
  20. static void ThreadAMethod()
  21. {
  22. for (int i = 0; i < 10; i++)
  23. {
  24. Console.WriteLine(Convert.ToString(i) + ": 线程A正在执行");
  25. Thread.Sleep(1000);
  26. }
  27. Console.WriteLine("线程A等待主线程退出……");
  28. mainThread.Join();  //等待主线程结束
  29. }
  30. }
  31. }
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace JoinLeadToDeadlock
{
class Program
{
static Thread mainThread;
static void Main(string[] args)
{
Console.WriteLine("主线程开始运行");
mainThread = Thread.CurrentThread; Thread ta = new Thread(new ThreadStart(ThreadAMethod));
ta.Start(); //线程A开始执行
Console.WriteLine("主线程等待线程A结束……");
ta.Join(); //等待线程A结束
Console.WriteLine("主线程退出");
} static void ThreadAMethod()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(Convert.ToString(i) + ": 线程A正在执行");
Thread.Sleep(1000);
}
Console.WriteLine("线程A等待主线程退出……");
mainThread.Join(); //等待主线程结束
}
}
}

在该例中,主线程mainThread先开始执行,然后启动线程ta,线程ta执行结束前又要等待mainThread线程执行结束,这样就出现了“交叉等待”的局面,必然死锁!

2)共享资源造成死锁

所谓“共享资源”,指的是多个线程可以同时访问的数据结构、文件等信息实体。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace SharedResourceLeadToDeadlock
  6. {
  7. class Program
  8. {
  9. //共享资源
  10. static SharedResource R1 = new SharedResource();
  11. static SharedResource R2 = new SharedResource();
  12. static void Main(string[] args)
  13. {
  14. Thread th1 = new Thread(UseSharedResource1);
  15. Thread th2 = new Thread(UseSharedResource2);
  16. th1.Start();
  17. th2.Start();
  18. //等待两线程运行结束
  19. th1.Join();
  20. th2.Join();
  21. }
  22. static void UseSharedResource1()
  23. {
  24. System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId);
  25. Monitor.Enter(R1);  //对R1加锁
  26. System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId);
  27. Thread.Sleep(1000);
  28. System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId);
  29. Monitor.Enter(R2);  //对R2加锁
  30. System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId);
  31. Thread.Sleep(1000);
  32. System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
  33. Monitor.Exit(R2);   //对R2解锁
  34. System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
  35. Monitor.Exit(R1);  //对R1解锁
  36. }
  37. static void UseSharedResource2()
  38. {
  39. System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId);
  40. Monitor.Enter(R2);   //对R2加锁
  41. System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId);
  42. Thread.Sleep(500);
  43. System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId);
  44. Monitor.Enter(R1);   //对R1加锁
  45. System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId);
  46. Thread.Sleep(500);
  47. System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
  48. Monitor.Exit(R1);  //对R1解锁
  49. System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
  50. Monitor.Exit(R2);   //对R2解锁
  51. }
  52. }
  53. class SharedResource
  54. {
  55. }
  56. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace SharedResourceLeadToDeadlock
  6. {
  7. class Program
  8. {
  9. //共享资源
  10. static SharedResource R1 = new SharedResource();
  11. static SharedResource R2 = new SharedResource();
  12. static void Main(string[] args)
  13. {
  14. Thread th1 = new Thread(UseSharedResource1);
  15. Thread th2 = new Thread(UseSharedResource2);
  16. th1.Start();
  17. th2.Start();
  18. //等待两线程运行结束
  19. th1.Join();
  20. th2.Join();
  21. }
  22. static void UseSharedResource1()
  23. {
  24. System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId);
  25. Monitor.Enter(R1);  //对R1加锁
  26. System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId);
  27. Thread.Sleep(1000);
  28. System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId);
  29. Monitor.Enter(R2);  //对R2加锁
  30. System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId);
  31. Thread.Sleep(1000);
  32. System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
  33. Monitor.Exit(R2);   //对R2解锁
  34. System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
  35. Monitor.Exit(R1);  //对R1解锁
  36. }
  37. static void UseSharedResource2()
  38. {
  39. System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId);
  40. Monitor.Enter(R2);   //对R2加锁
  41. System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId);
  42. Thread.Sleep(500);
  43. System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId);
  44. Monitor.Enter(R1);   //对R1加锁
  45. System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId);
  46. Thread.Sleep(500);
  47. System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
  48. Monitor.Exit(R1);  //对R1解锁
  49. System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
  50. Monitor.Exit(R2);   //对R2解锁
  51. }
  52. }
  53. class SharedResource
  54. {
  55. }
  56. }
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace SharedResourceLeadToDeadlock
{
class Program
{
//共享资源
static SharedResource R1 = new SharedResource();
static SharedResource R2 = new SharedResource(); static void Main(string[] args)
{
Thread th1 = new Thread(UseSharedResource1);
Thread th2 = new Thread(UseSharedResource2);
th1.Start();
th2.Start();
//等待两线程运行结束
th1.Join();
th2.Join();
} static void UseSharedResource1()
{
System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId);
Monitor.Enter(R1); //对R1加锁
System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000);
System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId);
Monitor.Enter(R2); //对R2加锁
System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000);
System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
Monitor.Exit(R2); //对R2解锁
System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
Monitor.Exit(R1); //对R1解锁
} static void UseSharedResource2()
{
System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId);
Monitor.Enter(R2); //对R2加锁
System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId);
Monitor.Enter(R1); //对R1加锁
System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
Monitor.Exit(R1); //对R1解锁
System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);
Monitor.Exit(R2); //对R2解锁
}
} class SharedResource
{
}
}

在该例中,线程th1执行时先申请使用R1,然后再申请使用R2,而线程th2执行时先申请R2,然后再申请R1,这样对于线程th1和th2,就会造成各自拥有一个对方需要的资源部释放,而又同时申请一个对方已经占有的资源,必然会造成死锁。

多线程数据存取错误

 
        当多个线程访问同一个数据时,如果不对读和写的顺序作出限定,例如一个线程正在读而另一个数据尝试写,则读数据的线程得到的数据就可能出错。这也是多线程带来的问题。如例:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace SharedResourceLeadToDataError
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. Thread[] ths = new Thread[4];
  12. for (int i = 0; i < 4; i++)
  13. {
  14. ths[i]=new Thread(increaseCount);
  15. ths[i].Start();
  16. }
  17. System.Console.ReadKey();
  18. }
  19. static void increaseCount()
  20. {
  21. Random ran = new Random();
  22. Thread.Sleep(ran.Next(100, 5000));
  23. int beginNum = SharedResource.Count;
  24. System.Console.WriteLine("线程 {0} 读到的起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNum );
  25. for (int i = 0; i < 10000; i++)
  26. {
  27. beginNum ++;
  28. }
  29. SharedResource.Count = beginNum;
  30. System.Console.WriteLine("线程 {0} 结束,SharedResource.Count={1}", Thread.CurrentThread.ManagedThreadId,SharedResource.Count);
  31. }
  32. }
  33. class SharedResource
  34. {
  35. public static int Count = 0;
  36. }
  37. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace SharedResourceLeadToDataError
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. Thread[] ths = new Thread[4];
  12. for (int i = 0; i < 4; i++)
  13. {
  14. ths[i]=new Thread(increaseCount);
  15. ths[i].Start();
  16. }
  17. System.Console.ReadKey();
  18. }
  19. static void increaseCount()
  20. {
  21. Random ran = new Random();
  22. Thread.Sleep(ran.Next(100, 5000));
  23. int beginNum = SharedResource.Count;
  24. System.Console.WriteLine("线程 {0} 读到的起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNum );
  25. for (int i = 0; i < 10000; i++)
  26. {
  27. beginNum ++;
  28. }
  29. SharedResource.Count = beginNum;
  30. System.Console.WriteLine("线程 {0} 结束,SharedResource.Count={1}", Thread.CurrentThread.ManagedThreadId,SharedResource.Count);
  31. }
  32. }
  33. class SharedResource
  34. {
  35. public static int Count = 0;
  36. }
  37. }
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace SharedResourceLeadToDataError
{
class Program
{
static void Main(string[] args)
{
Thread[] ths = new Thread[4];
for (int i = 0; i < 4; i++)
{
ths[i]=new Thread(increaseCount);
ths[i].Start();
}
System.Console.ReadKey();
} static void increaseCount()
{
Random ran = new Random();
Thread.Sleep(ran.Next(100, 5000));
int beginNum = SharedResource.Count;
System.Console.WriteLine("线程 {0} 读到的起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNum );
for (int i = 0; i < 10000; i++)
{
beginNum ++;
}
SharedResource.Count = beginNum;
System.Console.WriteLine("线程 {0} 结束,SharedResource.Count={1}", Thread.CurrentThread.ManagedThreadId,SharedResource.Count);
}
} class SharedResource
{
public static int Count = 0;
}
}
        四个线程同时读写共享变量ShareResource.Count,由于未对读写进行控制,所以必然会造成数据存取错误!
 

线程同步与并发访问控制手段

 
        正如为了解决车辆交通问题,人们建立了红绿灯的交通控制手段一样,可以为线程设定一套控制机制,以实现线程间的同步,以及保证以正确的顺序来访问共享资源。为了保护应用程序的资源不被破坏,为多线程程序提供了三种加锁的机制,分别是:Monitor类、Lock关键字和Mutex类。
 

1、Monitor类

 

(1)使用方法

 
  • Monitor对象的Enter方法可用于向共享资源申请一把“独占锁”。当一个线程拥有特定共享资源的独占锁时,尝试访问同一共享资源的其他线程只能等待。
  • Monitor对象的Exit方法用于释放锁。
  • 要注意:Enter与Exit方法必须严格配对,否则,有可能出现死锁情况。
  • Monitor可以锁定单个对象,也可以锁定一个类型的静态字段或属性
              1).Monitor.Enter(共享资源对象);
              2).Monitor.Enter(typeof(共享资源类型));
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. //展示用Monitor访问共享资源
  6. namespace UseMonitor1
  7. {
  8. class Program
  9. {
  10. static void Main(string[] args)
  11. {
  12. SharedResource obj = new SharedResource();
  13. Thread[] ths = new Thread[4];
  14. for (int i = 0; i < 4; i++)
  15. {
  16. ths[i] = new Thread(increaseCount);
  17. ths[i].Start(obj);
  18. }
  19. System.Console.ReadKey();
  20. }
  21. static void increaseCount(Object obj)
  22. {
  23. //访问实例字段
  24. VisitDynamicField(obj);
  25. //访问静态字段
  26. VisitStaticField();
  27. }
  28. //访问静态字段
  29. private static void VisitStaticField()
  30. {
  31. //访问静态字段
  32. Monitor.Enter(typeof(SharedResource));
  33. int beginNumber = SharedResource.StaticCount;
  34. System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);
  35. for (int i = 0; i < 10000; i++)
  36. {
  37. beginNumber++;
  38. }
  39. SharedResource.StaticCount = beginNumber;
  40. System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}",
  41. Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount);
  42. Monitor.Exit(typeof(SharedResource));
  43. }
  44. //访问实例字段
  45. private static void VisitDynamicField(Object obj)
  46. {
  47. Monitor.Enter(obj);
  48. int beginNumber = (obj as SharedResource).DynamicCount;
  49. System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);
  50. for (int i = 0; i < 10000; i++)
  51. {
  52. beginNumber++;
  53. }
  54. (obj as SharedResource).DynamicCount = beginNumber;
  55. System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}",
  56. Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount);
  57. Monitor.Exit(obj);
  58. }
  59. }
  60. //共享资源类
  61. class SharedResource
  62. {
  63. public int DynamicCount = 0;        //多线程共享的实例字段
  64. public static int StaticCount = 0;  //多线程共享的静态字段
  65. }
  66. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. //展示用Monitor访问共享资源
  6. namespace UseMonitor1
  7. {
  8. class Program
  9. {
  10. static void Main(string[] args)
  11. {
  12. SharedResource obj = new SharedResource();
  13. Thread[] ths = new Thread[4];
  14. for (int i = 0; i < 4; i++)
  15. {
  16. ths[i] = new Thread(increaseCount);
  17. ths[i].Start(obj);
  18. }
  19. System.Console.ReadKey();
  20. }
  21. static void increaseCount(Object obj)
  22. {
  23. //访问实例字段
  24. VisitDynamicField(obj);
  25. //访问静态字段
  26. VisitStaticField();
  27. }
  28. //访问静态字段
  29. private static void VisitStaticField()
  30. {
  31. //访问静态字段
  32. Monitor.Enter(typeof(SharedResource));
  33. int beginNumber = SharedResource.StaticCount;
  34. System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);
  35. for (int i = 0; i < 10000; i++)
  36. {
  37. beginNumber++;
  38. }
  39. SharedResource.StaticCount = beginNumber;
  40. System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}",
  41. Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount);
  42. Monitor.Exit(typeof(SharedResource));
  43. }
  44. //访问实例字段
  45. private static void VisitDynamicField(Object obj)
  46. {
  47. Monitor.Enter(obj);
  48. int beginNumber = (obj as SharedResource).DynamicCount;
  49. System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);
  50. for (int i = 0; i < 10000; i++)
  51. {
  52. beginNumber++;
  53. }
  54. (obj as SharedResource).DynamicCount = beginNumber;
  55. System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}",
  56. Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount);
  57. Monitor.Exit(obj);
  58. }
  59. }
  60. //共享资源类
  61. class SharedResource
  62. {
  63. public int DynamicCount = 0;        //多线程共享的实例字段
  64. public static int StaticCount = 0;  //多线程共享的静态字段
  65. }
  66. }
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; //展示用Monitor访问共享资源
namespace UseMonitor1
{
class Program
{
static void Main(string[] args)
{
SharedResource obj = new SharedResource(); Thread[] ths = new Thread[4];
for (int i = 0; i < 4; i++)
{
ths[i] = new Thread(increaseCount);
ths[i].Start(obj);
}
System.Console.ReadKey();
}
static void increaseCount(Object obj)
{
//访问实例字段
VisitDynamicField(obj);
//访问静态字段
VisitStaticField();
} //访问静态字段
private static void VisitStaticField()
{
//访问静态字段
Monitor.Enter(typeof(SharedResource)); int beginNumber = SharedResource.StaticCount;
System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNumber);
for (int i = 0; i < 10000; i++)
{
beginNumber++;
}
SharedResource.StaticCount = beginNumber;
System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}",
Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount); Monitor.Exit(typeof(SharedResource));
} //访问实例字段
private static void VisitDynamicField(Object obj)
{
Monitor.Enter(obj); int beginNumber = (obj as SharedResource).DynamicCount;
System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNumber);
for (int i = 0; i < 10000; i++)
{
beginNumber++;
}
(obj as SharedResource).DynamicCount = beginNumber;
System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}",
Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount); Monitor.Exit(obj);
}
}
//共享资源类
class SharedResource
{
public int DynamicCount = 0; //多线程共享的实例字段
public static int StaticCount = 0; //多线程共享的静态字段
}
}

Monitor类的使用模板:

Monitor.Enter(共享资源对象); //申请对象锁

        //得到了对象锁,可以对共享资源进行访问,
        //其他线程只能等待
        //访问共享资源
        //对共享资源的访问完成,释放对象锁,
        //让其他线程有机会访问共享资源
        Monitor.Exit(obj);

(2)Monitor的特殊注意之处:

 
        Monitor一般只用于访问引用类型的共享资源,如果将其施加于值类型变量,则值类型变量将会被装箱,而当调用Exit方法时,虽然是同一个值类型变量,但实际上此值类型变量又会被第二次装箱,这将导致Enter方法所访问的对象与Exit方法所访问的不是同一个,Monitor对象将会引发SynchronizationLockException。
        因此,不要将Monitor用于值类型!
 

(3) Monitor.Wait()和Monitor.Pulse()

 
        Wait()释放对象上的锁,以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。
        Pulse(),PulseAll()向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。PulseAll与Pulse方法类似,不过它是向所有在阻塞队列中的进程发送通知信号,如果只有一个线程被阻塞,那么请使用Pulse方法。
        注意:Pulse、PulseAll和Wait方法必须从同步的代码块内调用。
        例1:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace UseMonitor2
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. //创建共享资源
  12. SharedResource obj = new SharedResource();
  13. //创建线程对象并启动
  14. Thread tha = new Thread(ThreadMethodA);
  15. Thread thb = new Thread(ThreadMethodB);
  16. tha.Start(obj);
  17. thb.Start(obj);
  18. //程序暂停
  19. System.Console.ReadKey();
  20. }
  21. static void ThreadMethodA(Object obj)
  22. {
  23. Monitor.Enter(obj);
  24. (obj as SharedResource).DynamicCount += 100;
  25. System.Console.WriteLine("线程A完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount);
  26. Monitor.Pulse(obj); //通知B线程进入准备队列
  27. Monitor.Exit(obj);
  28. }
  29. static void ThreadMethodB(Object obj)
  30. {
  31. Monitor.Enter(obj);
  32. //A线程还未工作,因为字段保持初始值0
  33. //如果注释掉此条件判断语句,则有可能会发生死锁
  34. if((obj as SharedResource).DynamicCount == 0)
  35. Monitor.Wait(obj);//将本线程阻塞,进入阻塞队列等待
  36. (obj as SharedResource).DynamicCount += 100;
  37. System.Console.WriteLine("线程B完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount);
  38. Monitor.Exit(obj);
  39. }
  40. }
  41. //共享资源类
  42. class SharedResource
  43. {
  44. public int DynamicCount = 0;        //多线程共享的实例字段
  45. }
  46. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace UseMonitor2
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. //创建共享资源
  12. SharedResource obj = new SharedResource();
  13. //创建线程对象并启动
  14. Thread tha = new Thread(ThreadMethodA);
  15. Thread thb = new Thread(ThreadMethodB);
  16. tha.Start(obj);
  17. thb.Start(obj);
  18. //程序暂停
  19. System.Console.ReadKey();
  20. }
  21. static void ThreadMethodA(Object obj)
  22. {
  23. Monitor.Enter(obj);
  24. (obj as SharedResource).DynamicCount += 100;
  25. System.Console.WriteLine("线程A完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount);
  26. Monitor.Pulse(obj); //通知B线程进入准备队列
  27. Monitor.Exit(obj);
  28. }
  29. static void ThreadMethodB(Object obj)
  30. {
  31. Monitor.Enter(obj);
  32. //A线程还未工作,因为字段保持初始值0
  33. //如果注释掉此条件判断语句,则有可能会发生死锁
  34. if((obj as SharedResource).DynamicCount == 0)
  35. Monitor.Wait(obj);//将本线程阻塞,进入阻塞队列等待
  36. (obj as SharedResource).DynamicCount += 100;
  37. System.Console.WriteLine("线程B完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount);
  38. Monitor.Exit(obj);
  39. }
  40. }
  41. //共享资源类
  42. class SharedResource
  43. {
  44. public int DynamicCount = 0;        //多线程共享的实例字段
  45. }
  46. }
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace UseMonitor2
{
class Program
{
static void Main(string[] args)
{
//创建共享资源
SharedResource obj = new SharedResource();
//创建线程对象并启动
Thread tha = new Thread(ThreadMethodA);
Thread thb = new Thread(ThreadMethodB);
tha.Start(obj);
thb.Start(obj); //程序暂停
System.Console.ReadKey();
} static void ThreadMethodA(Object obj)
{
Monitor.Enter(obj);
(obj as SharedResource).DynamicCount += 100;
System.Console.WriteLine("线程A完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount);
Monitor.Pulse(obj); //通知B线程进入准备队列
Monitor.Exit(obj);
} static void ThreadMethodB(Object obj)
{
Monitor.Enter(obj);
//A线程还未工作,因为字段保持初始值0
//如果注释掉此条件判断语句,则有可能会发生死锁
if((obj as SharedResource).DynamicCount == 0)
Monitor.Wait(obj);//将本线程阻塞,进入阻塞队列等待
(obj as SharedResource).DynamicCount += 100;
System.Console.WriteLine("线程B完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount);
Monitor.Exit(obj);
}
} //共享资源类
class SharedResource
{
public int DynamicCount = 0; //多线程共享的实例字段
}
}
 

例2:

  1. using System.Threading;
  2. public class Program
  3. {
  4. static object ball = new object();
  5. public static void Main()
  6. {
  7. Thread threadPing = new Thread( ThreadPingProc );
  8. Thread threadPong = new Thread( ThreadPongProc );
  9. threadPing.Start(); threadPong.Start();
  10. }
  11. static void ThreadPongProc()
  12. {
  13. System.Console.WriteLine("ThreadPong: Hello!");
  14. lock ( ball )
  15. for (int i = 0; i < 5; i++)
  16. {
  17. System.Console.WriteLine("ThreadPong: Pong ");
  18. Monitor.Pulse( ball );
  19. Monitor.Wait( ball );
  20. }
  21. System.Console.WriteLine("ThreadPong: Bye!");
  22. }
  23. static void ThreadPingProc()
  24. {
  25. System.Console.WriteLine("ThreadPing: Hello!");
  26. lock ( ball )
  27. for(int i=0; i< 5; i++)
  28. {
  29. System.Console.WriteLine("ThreadPing: Ping ");
  30. Monitor.Pulse( ball );
  31. Monitor.Wait( ball );
  32. }
  33. System.Console.WriteLine("ThreadPing: Bye!");
  34. }
  35. }
  1. using System.Threading;
  2. public class Program
  3. {
  4. static object ball = new object();
  5. public static void Main()
  6. {
  7. Thread threadPing = new Thread( ThreadPingProc );
  8. Thread threadPong = new Thread( ThreadPongProc );
  9. threadPing.Start(); threadPong.Start();
  10. }
  11. static void ThreadPongProc()
  12. {
  13. System.Console.WriteLine("ThreadPong: Hello!");
  14. lock ( ball )
  15. for (int i = 0; i < 5; i++)
  16. {
  17. System.Console.WriteLine("ThreadPong: Pong ");
  18. Monitor.Pulse( ball );
  19. Monitor.Wait( ball );
  20. }
  21. System.Console.WriteLine("ThreadPong: Bye!");
  22. }
  23. static void ThreadPingProc()
  24. {
  25. System.Console.WriteLine("ThreadPing: Hello!");
  26. lock ( ball )
  27. for(int i=0; i< 5; i++)
  28. {
  29. System.Console.WriteLine("ThreadPing: Ping ");
  30. Monitor.Pulse( ball );
  31. Monitor.Wait( ball );
  32. }
  33. System.Console.WriteLine("ThreadPing: Bye!");
  34. }
  35. }
using System.Threading;
public class Program
{
static object ball = new object();
public static void Main()
{
Thread threadPing = new Thread( ThreadPingProc );
Thread threadPong = new Thread( ThreadPongProc );
threadPing.Start(); threadPong.Start();
}
static void ThreadPongProc()
{
System.Console.WriteLine("ThreadPong: Hello!");
lock ( ball )
for (int i = 0; i < 5; i++)
{
System.Console.WriteLine("ThreadPong: Pong ");
Monitor.Pulse( ball );
Monitor.Wait( ball );
}
System.Console.WriteLine("ThreadPong: Bye!");
}
static void ThreadPingProc()
{
System.Console.WriteLine("ThreadPing: Hello!");
lock ( ball )
for(int i=0; i< 5; i++)
{
System.Console.WriteLine("ThreadPing: Ping ");
Monitor.Pulse( ball );
Monitor.Wait( ball );
}
System.Console.WriteLine("ThreadPing: Bye!");
}
}
可能的执行结果:
  1. ThreadPing: Hello!
  2. ThreadPing: Ping
  3. ThreadPong: Hello!
  4. ThreadPong: Pong
  5. ThreadPing: Ping
  6. ThreadPong: Pong
  7. ThreadPing: Ping
  8. ThreadPong: Pong
  9. ThreadPing: Ping
  10. ThreadPong: Pong
  11. ThreadPing: Ping
  12. ThreadPong: Pong
  13. ThreadPing: Bye!
  1. ThreadPing: Hello!
  2. ThreadPing: Ping
  3. ThreadPong: Hello!
  4. ThreadPong: Pong
  5. ThreadPing: Ping
  6. ThreadPong: Pong
  7. ThreadPing: Ping
  8. ThreadPong: Pong
  9. ThreadPing: Ping
  10. ThreadPong: Pong
  11. ThreadPing: Ping
  12. ThreadPong: Pong
  13. ThreadPing: Bye!
ThreadPing: Hello!
ThreadPing: Ping
ThreadPong: Hello!
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Bye!
        当threadPing进程进入ThreadPingProc锁定ball并调用Monitor.Pulse( ball )后,它通知threadPong从阻塞队列进入准备队列,当threadPing调用Monitor.Wait( ball )阻塞自己后,它放弃了了对ball的锁定,所以threadPong得以执行。        
           因此,可以借助Monitor.Pulse()来控制进程的推进顺序。

  1. //A线程执行的代码
  2. lock(obj)
  3. {
  4. //访问共享资源obj
  5. Monitor.Pulse(obj); //通知B 线程可以访问共享资源obj了
  6. }
  7. ---------------------------------------------------------------
  8. //B线程执行的代码
  9. lock(obj)
  10. {
  11. Monitor.Wait(obj); //等待A 线程完成
  12. //访问共享资源obj
  13. }
  1. //A线程执行的代码
  2. lock(obj)
  3. {
  4. //访问共享资源obj
  5. Monitor.Pulse(obj); //通知B 线程可以访问共享资源obj了
  6. }
  7. ---------------------------------------------------------------
  8. //B线程执行的代码
  9. lock(obj)
  10. {
  11. Monitor.Wait(obj); //等待A 线程完成
  12. //访问共享资源obj
  13. }
//A线程执行的代码
lock(obj)
{
//访问共享资源obj
Monitor.Pulse(obj); //通知B 线程可以访问共享资源obj了
}
---------------------------------------------------------------
//B线程执行的代码
lock(obj)
{
Monitor.Wait(obj); //等待A 线程完成
//访问共享资源obj
}

2、Lock关键字

        C#使用Lock关键字来简化Monitor的用法。lock就是对Monitor的Enter和Exit的一个封装,而且使用起来更简洁,因此Monitor类的Enter()和Exit()方法的组合使用可以用lock关键字替代。
 
lock (obj)
{
       //访问共享资源代码段
}
等价于:
Monitor.Enter(obj);
        //访问共享资源代码段
Monitor.Exit(obj);
 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. //展示用Monitor访问共享资源
  6. namespace UseMonitor1
  7. {
  8. class Program
  9. {
  10. static void Main(string[] args)
  11. {
  12. SharedResource obj = new SharedResource();
  13. Thread[] ths = new Thread[4];
  14. for (int i = 0; i < 4; i++)
  15. {
  16. ths[i] = new Thread(increaseCount);
  17. ths[i].Start(obj);
  18. }
  19. System.Console.ReadKey();
  20. }
  21. static void increaseCount(Object obj)
  22. {
  23. //访问实例字段
  24. VisitDynamicField(obj);
  25. //访问静态字段
  26. VisitStaticField();
  27. }
  28. //访问静态字段
  29. private static void VisitStaticField()
  30. {
  31. //访问静态字段
  32. lock (typeof(SharedResource))
  33. {
  34. int beginNumber = SharedResource.StaticCount;
  35. System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);
  36. for (int i = 0; i < 10000; i++)
  37. {
  38. beginNumber++;
  39. }
  40. SharedResource.StaticCount = beginNumber;
  41. System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}",
  42. Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount);
  43. }
  44. }
  45. //访问实例字段
  46. private static void VisitDynamicField(Object obj)
  47. {
  48. lock (obj)
  49. {
  50. int beginNumber = (obj as SharedResource).DynamicCount;
  51. System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);
  52. for (int i = 0; i < 10000; i++)
  53. {
  54. beginNumber++;
  55. }
  56. (obj as SharedResource).DynamicCount = beginNumber;
  57. System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}",
  58. Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount);
  59. }
  60. }
  61. }
  62. //共享资源类
  63. class SharedResource
  64. {
  65. public int DynamicCount = 0;        //多线程共享的实例字段
  66. public static int StaticCount = 0;  //多线程共享的静态字段
  67. }
  68. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. //展示用Monitor访问共享资源
  6. namespace UseMonitor1
  7. {
  8. class Program
  9. {
  10. static void Main(string[] args)
  11. {
  12. SharedResource obj = new SharedResource();
  13. Thread[] ths = new Thread[4];
  14. for (int i = 0; i < 4; i++)
  15. {
  16. ths[i] = new Thread(increaseCount);
  17. ths[i].Start(obj);
  18. }
  19. System.Console.ReadKey();
  20. }
  21. static void increaseCount(Object obj)
  22. {
  23. //访问实例字段
  24. VisitDynamicField(obj);
  25. //访问静态字段
  26. VisitStaticField();
  27. }
  28. //访问静态字段
  29. private static void VisitStaticField()
  30. {
  31. //访问静态字段
  32. lock (typeof(SharedResource))
  33. {
  34. int beginNumber = SharedResource.StaticCount;
  35. System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);
  36. for (int i = 0; i < 10000; i++)
  37. {
  38. beginNumber++;
  39. }
  40. SharedResource.StaticCount = beginNumber;
  41. System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}",
  42. Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount);
  43. }
  44. }
  45. //访问实例字段
  46. private static void VisitDynamicField(Object obj)
  47. {
  48. lock (obj)
  49. {
  50. int beginNumber = (obj as SharedResource).DynamicCount;
  51. System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);
  52. for (int i = 0; i < 10000; i++)
  53. {
  54. beginNumber++;
  55. }
  56. (obj as SharedResource).DynamicCount = beginNumber;
  57. System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}",
  58. Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount);
  59. }
  60. }
  61. }
  62. //共享资源类
  63. class SharedResource
  64. {
  65. public int DynamicCount = 0;        //多线程共享的实例字段
  66. public static int StaticCount = 0;  //多线程共享的静态字段
  67. }
  68. }
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; //展示用Monitor访问共享资源
namespace UseMonitor1
{
class Program
{
static void Main(string[] args)
{
SharedResource obj = new SharedResource(); Thread[] ths = new Thread[4];
for (int i = 0; i < 4; i++)
{
ths[i] = new Thread(increaseCount);
ths[i].Start(obj);
}
System.Console.ReadKey();
}
static void increaseCount(Object obj)
{
//访问实例字段
VisitDynamicField(obj);
//访问静态字段
VisitStaticField();
} //访问静态字段
private static void VisitStaticField()
{
//访问静态字段
lock (typeof(SharedResource))
{
int beginNumber = SharedResource.StaticCount;
System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNumber);
for (int i = 0; i < 10000; i++)
{
beginNumber++;
}
SharedResource.StaticCount = beginNumber;
System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}",
Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount);
}
} //访问实例字段
private static void VisitDynamicField(Object obj)
{
lock (obj)
{
int beginNumber = (obj as SharedResource).DynamicCount;
System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1} ", Thread.CurrentThread.ManagedThreadId, beginNumber);
for (int i = 0; i < 10000; i++)
{
beginNumber++;
}
(obj as SharedResource).DynamicCount = beginNumber;
System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}",
Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount);
}
}
}
//共享资源类
class SharedResource
{
public int DynamicCount = 0; //多线程共享的实例字段
public static int StaticCount = 0; //多线程共享的静态字段
}
}

3、自旋锁SpinLock

 
        当一个线程需要访问共享资源时,它可以调用SpinLock.Enter或SpinLock.TryEnter方法申请独占锁,如果暂时不能获得锁(这时可能运行于另一个CPU核上的线程正在访问共享资源),当前线程就会“空转”若干个时钟周期,然后再次尝试。在这个过程中,线程的状态仍是Running,从而避免了操作系统进行一次线程上下文切换所带来的开销。
 
  1. public class MyType
  2. {
  3. //创建自旋锁对象
  4. private SpinLock _spinLock = new SpinLock();
  5. //将被多线程执行的代码,
  6. //由于使用了自旋锁,可以保证被“锁定”代码一次只会被一个线程执行
  7. public void DoWork()
  8. {
  9. bool lockTaken = false;
  10. try
  11. {
  12. _spinLock.Enter(ref lockTaken); //申请获取“锁”
  13. // 获得了锁,在此书写工作代码,这些工作代码不会同时被两个线程执行
  14. }
  15. finally
  16. {
  17. //工作完毕,或者发生异常时,检查一下当前线程是否占有了锁
  18. //如果占有了锁,释放它,以避免出现死锁的情况。
  19. if (lockTaken)  _spinLock.Exit();
  20. }
  21. }
  22. }
  1. public class MyType
  2. {
  3. //创建自旋锁对象
  4. private SpinLock _spinLock = new SpinLock();
  5. //将被多线程执行的代码,
  6. //由于使用了自旋锁,可以保证被“锁定”代码一次只会被一个线程执行
  7. public void DoWork()
  8. {
  9. bool lockTaken = false;
  10. try
  11. {
  12. _spinLock.Enter(ref lockTaken); //申请获取“锁”
  13. // 获得了锁,在此书写工作代码,这些工作代码不会同时被两个线程执行
  14. }
  15. finally
  16. {
  17. //工作完毕,或者发生异常时,检查一下当前线程是否占有了锁
  18. //如果占有了锁,释放它,以避免出现死锁的情况。
  19. if (lockTaken)  _spinLock.Exit();
  20. }
  21. }
  22. }
public class MyType
{
//创建自旋锁对象
private SpinLock _spinLock = new SpinLock();
//将被多线程执行的代码,
//由于使用了自旋锁,可以保证被“锁定”代码一次只会被一个线程执行
public void DoWork()
{
bool lockTaken = false;
try
{
_spinLock.Enter(ref lockTaken); //申请获取“锁”
// 获得了锁,在此书写工作代码,这些工作代码不会同时被两个线程执行
}
finally
{
//工作完毕,或者发生异常时,检查一下当前线程是否占有了锁
//如果占有了锁,释放它,以避免出现死锁的情况。
if (lockTaken) _spinLock.Exit();
}
}
}
 

4、实现原子操作——Interlocked类

 
        Interlocked类是一种互锁操作,提供对多个线程共享的变量进行同步访问的方法,互锁操作具有原子性,即整个操作时不能由相同变量上的另一个互锁操作所中断的单元。
        这个类提供了Increment、Decrement、Add静态方法用于对int或long型变量的递增、递减或相加操作。还提供了Exchange(为整型或引用对象赋值)、CompareExchange(比较后再对整型或引用对象赋值),用于为整型或引用类型的赋值提供原子操作。
        在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
  1. 将实例变量中的值加载到寄存器中。
  2. 增加或减少该值。
  3. 在实例变量中存储该值。

如果不使用 Increment 和 Decrement,线程会在执行完前两个步骤后被抢先。 然后由另一个线程执行所有三个步骤。 当第一个线程重新开始执行时,它覆盖实例变量中的值,造成第二个线程执行增减操作的结果丢失。

 
利用Interlocked类类解决生产者-消费者关系中的竞争条件问题:(例子来自《周长发——c#面向对象编程》
  1. // Interlocked.cs
  2. // Interlocked示例
  3. using System;
  4. using System.Threading;
  5. class Test
  6. {
  7. private long bufferEmpty = 0;
  8. private string buffer = null;
  9. static void Main()
  10. {
  11. Test t = new Test();
  12. // 进行测试
  13. t.Go();
  14. }
  15. public void Go()
  16. {
  17. Thread t1 = new Thread(new ThreadStart(Producer));
  18. t1.Name = "生产者线程";
  19. t1.Start();
  20. Thread t2 = new Thread(new ThreadStart(Consumer));
  21. t2.Name = "消费者线程";
  22. t2.Start();
  23. // 等待两个线程结束
  24. t1.Join();
  25. t2.Join();
  26. }
  27. // 生产者方法
  28. public void Producer()
  29. {
  30. Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);
  31. try
  32. {
  33. for (int j = 0; j < 16; ++j)
  34. {
  35. // 等待共享缓冲区为空
  36. while (Interlocked.Read(ref bufferEmpty) != 0)
  37. Thread.Sleep(100);
  38. // 构造共享缓冲区
  39. Random r = new Random();
  40. int bufSize = r.Next() % 64;
  41. char[] s = new char[bufSize];
  42. for (int i = 0; i < bufSize; ++i)
  43. {
  44. s[i] = (char)((int)'A' + r.Next() % 26);
  45. }
  46. buffer = new string(s);
  47. Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer);
  48. // 互锁加一,成为1,标志共享缓冲区已满
  49. Interlocked.Increment(ref bufferEmpty);
  50. // 休眠,将时间片让给消费者
  51. Thread.Sleep(10);
  52. }
  53. Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
  54. }
  55. catch (System.Threading.ThreadInterruptedException)
  56. {
  57. Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
  58. }
  59. }
  60. // 消费者方法
  61. public void Consumer()
  62. {
  63. Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);
  64. try
  65. {
  66. for (int j = 0; j < 16; ++j)
  67. {
  68. while (Interlocked.Read(ref bufferEmpty) == 0)
  69. Thread.Sleep(100);
  70. // 打印共享缓冲区
  71. Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer);
  72. // 互锁减一,成为0,标志共享缓冲区已空
  73. Interlocked.Decrement(ref bufferEmpty);
  74. // 休眠,将时间片让给生产者
  75. Thread.Sleep(10);
  76. }
  77. Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
  78. }
  79. catch (System.Threading.ThreadInterruptedException)
  80. {
  81. Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
  82. }
  83. }
  84. }
  1. // Interlocked.cs
  2. // Interlocked示例
  3. using System;
  4. using System.Threading;
  5. class Test
  6. {
  7. private long bufferEmpty = 0;
  8. private string buffer = null;
  9. static void Main()
  10. {
  11. Test t = new Test();
  12. // 进行测试
  13. t.Go();
  14. }
  15. public void Go()
  16. {
  17. Thread t1 = new Thread(new ThreadStart(Producer));
  18. t1.Name = "生产者线程";
  19. t1.Start();
  20. Thread t2 = new Thread(new ThreadStart(Consumer));
  21. t2.Name = "消费者线程";
  22. t2.Start();
  23. // 等待两个线程结束
  24. t1.Join();
  25. t2.Join();
  26. }
  27. // 生产者方法
  28. public void Producer()
  29. {
  30. Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);
  31. try
  32. {
  33. for (int j = 0; j < 16; ++j)
  34. {
  35. // 等待共享缓冲区为空
  36. while (Interlocked.Read(ref bufferEmpty) != 0)
  37. Thread.Sleep(100);
  38. // 构造共享缓冲区
  39. Random r = new Random();
  40. int bufSize = r.Next() % 64;
  41. char[] s = new char[bufSize];
  42. for (int i = 0; i < bufSize; ++i)
  43. {
  44. s[i] = (char)((int)'A' + r.Next() % 26);
  45. }
  46. buffer = new string(s);
  47. Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer);
  48. // 互锁加一,成为1,标志共享缓冲区已满
  49. Interlocked.Increment(ref bufferEmpty);
  50. // 休眠,将时间片让给消费者
  51. Thread.Sleep(10);
  52. }
  53. Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
  54. }
  55. catch (System.Threading.ThreadInterruptedException)
  56. {
  57. Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
  58. }
  59. }
  60. // 消费者方法
  61. public void Consumer()
  62. {
  63. Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);
  64. try
  65. {
  66. for (int j = 0; j < 16; ++j)
  67. {
  68. while (Interlocked.Read(ref bufferEmpty) == 0)
  69. Thread.Sleep(100);
  70. // 打印共享缓冲区
  71. Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer);
  72. // 互锁减一,成为0,标志共享缓冲区已空
  73. Interlocked.Decrement(ref bufferEmpty);
  74. // 休眠,将时间片让给生产者
  75. Thread.Sleep(10);
  76. }
  77. Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
  78. }
  79. catch (System.Threading.ThreadInterruptedException)
  80. {
  81. Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
  82. }
  83. }
  84. }
// Interlocked.cs
// Interlocked示例 using System;
using System.Threading; class Test
{
private long bufferEmpty = 0;
private string buffer = null; static void Main()
{
Test t = new Test();
// 进行测试
t.Go();
} public void Go()
{
Thread t1 = new Thread(new ThreadStart(Producer));
t1.Name = "生产者线程";
t1.Start(); Thread t2 = new Thread(new ThreadStart(Consumer));
t2.Name = "消费者线程";
t2.Start(); // 等待两个线程结束
t1.Join();
t2.Join();
} // 生产者方法
public void Producer()
{
Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name); try
{
for (int j = 0; j < 16; ++j)
{
// 等待共享缓冲区为空
while (Interlocked.Read(ref bufferEmpty) != 0)
Thread.Sleep(100); // 构造共享缓冲区
Random r = new Random();
int bufSize = r.Next() % 64;
char[] s = new char[bufSize];
for (int i = 0; i < bufSize; ++i)
{
s[i] = (char)((int)'A' + r.Next() % 26);
}
buffer = new string(s); Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer); // 互锁加一,成为1,标志共享缓冲区已满
Interlocked.Increment(ref bufferEmpty); // 休眠,将时间片让给消费者
Thread.Sleep(10);
} Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
}
catch (System.Threading.ThreadInterruptedException)
{
Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
}
} // 消费者方法
public void Consumer()
{
Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name); try
{
for (int j = 0; j < 16; ++j)
{
while (Interlocked.Read(ref bufferEmpty) == 0)
Thread.Sleep(100); // 打印共享缓冲区
Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer); // 互锁减一,成为0,标志共享缓冲区已空
Interlocked.Decrement(ref bufferEmpty); // 休眠,将时间片让给生产者
Thread.Sleep(10);
} Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
}
catch (System.Threading.ThreadInterruptedException)
{
Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
}
}
}

5、Mutex类

 
         Mutex与Monitor类似,需要注意的是Mutex分两种:一种是本地Mutex一种是系统级Mutex,系统级Mutex可以用来进行跨进程间的线程的同步。尽管 mutex 可以用于进程内的线程同步,但是使用 Monitor 通常更为可取,因为监视器是专门为 .NET Framework 而设计的,因而它可以更好地利用资源。相比之下,Mutex 类是 Win32 构造的包装。尽管 mutex 比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算资源。
        一个线程要想访问共享资源,它必须调用Mutex对象的Wait系列方法之一提出申请。当申请得到批准的线程完成了对于共享资源的访问后,它调用Mutex对象的ReleaseMutex()方法释放对于共享资源的访问权。
 
       利用多线程模拟3个人在ATM上多次提款操作:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace UseATM
  6. {
  7. class Program
  8. {
  9. static ATM OneATM=new ATM(); //共享资源
  10. static void Main(string[] args)
  11. {
  12. //向公共帐号存款2万
  13. Console.Write("输入公司公共帐户的金额:");
  14. int PublicAcountMoney =Convert.ToInt32(Console.ReadLine());
  15. OneATM.Deposit(PublicAcountMoney);
  16. Console.Write("输入ATM中的现金额:");
  17. int ATMLeftMoney = Convert.ToInt32(Console.ReadLine());
  18. OneATM.SetATMLeftMoney(ATMLeftMoney);
  19. System.Console.WriteLine("\n敲任意键从公共帐户中取钱,ESC键退出……\n");
  20. while (System.Console.ReadKey(true).Key !=ConsoleKey.Escape)
  21. {
  22. System.Console.WriteLine("");
  23. Thread One = new Thread(WithDrawMoney);
  24. Thread Two = new Thread(WithDrawMoney);
  25. Thread Three = new Thread(WithDrawMoney);
  26. //随机生成一个要提款的数额,最少100元,最高5000元
  27. Random ran = new Random();
  28. One.Start(ran.Next(100, 5000));
  29. Two.Start(ran.Next(100, 5000));
  30. Three.Start(ran.Next(100, 5000));
  31. //等三人取完钱
  32. One.Join();
  33. Two.Join();
  34. Three.Join();
  35. System.Console.WriteLine("公共账号剩余{0}元,ATM中可提现金:{1}", OneATM.QueryPublicAccount(),OneATM.QueryATMLeftAccount());
  36. }
  37. }
  38. //线程函数
  39. static void WithDrawMoney(object amount)
  40. {
  41. switch(OneATM.WithDraw((int)amount))
  42. {
  43. case WithDrawState.Succeed:
  44. System.Console.WriteLine("成功取出{0}元。",amount );
  45. break;
  46. case WithDrawState.ATMHasNotEnoughCash:
  47. System.Console.WriteLine("ATM中现金不足,无法支取{0}元。", amount);
  48. break ;
  49. case WithDrawState.AccountHasNotEnoughMoney:
  50. System.Console.WriteLine("帐户中没钱了!无法取出{0}元",amount);
  51. break ;
  52. }
  53. }
  54. }
  55. //自助取款机
  56. class ATM
  57. {
  58. private int PublicAcountLeftMoney;//帐户剩余的钱
  59. private int ATMLeftMoney;//提款机剩余的钱
  60. //同步信息号量
  61. private Mutex m = new Mutex();
  62. //取钱
  63. public WithDrawState WithDraw(int amount)
  64. {
  65. m.WaitOne();
  66. //公共帐号钱不够
  67. if (PublicAcountLeftMoney < amount)
  68. {
  69. m.ReleaseMutex();
  70. return WithDrawState.AccountHasNotEnoughMoney;
  71. }
  72. //ATM现金不够
  73. if (ATMLeftMoney < amount)
  74. {
  75. m.ReleaseMutex();
  76. return WithDrawState.ATMHasNotEnoughCash;
  77. }
  78. //用户可以提取现金
  79. ATMLeftMoney -= amount;
  80. PublicAcountLeftMoney -= amount;
  81. m.ReleaseMutex();
  82. return WithDrawState.Succeed;
  83. }
  84. //存钱
  85. public void Deposit(int amount)
  86. {
  87. m.WaitOne();
  88. PublicAcountLeftMoney += amount;
  89. m.ReleaseMutex();
  90. }
  91. /// <summary>
  92. /// 设置ATM的现金金额
  93. /// </summary>
  94. /// <param name="amount"></param>
  95. public void SetATMLeftMoney(int amount)
  96. {
  97. Interlocked.Exchange(ref ATMLeftMoney, amount);
  98. }
  99. //获取还剩余多少钱
  100. public int QueryPublicAccount()
  101. {
  102. return PublicAcountLeftMoney;
  103. }
  104. /// <summary>
  105. /// 查询ATM剩余多少钱
  106. /// </summary>
  107. /// <returns></returns>
  108. public int QueryATMLeftAccount()
  109. {
  110. return ATMLeftMoney;
  111. }
  112. }
  113. //取款状态
  114. public enum WithDrawState
  115. {
  116. Succeed,        //取钱成功
  117. AccountHasNotEnoughMoney, //账号中没钱了
  118. ATMHasNotEnoughCash  //ATM中没有足够的现金
  119. }
  120. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace UseATM
  6. {
  7. class Program
  8. {
  9. static ATM OneATM=new ATM(); //共享资源
  10. static void Main(string[] args)
  11. {
  12. //向公共帐号存款2万
  13. Console.Write("输入公司公共帐户的金额:");
  14. int PublicAcountMoney =Convert.ToInt32(Console.ReadLine());
  15. OneATM.Deposit(PublicAcountMoney);
  16. Console.Write("输入ATM中的现金额:");
  17. int ATMLeftMoney = Convert.ToInt32(Console.ReadLine());
  18. OneATM.SetATMLeftMoney(ATMLeftMoney);
  19. System.Console.WriteLine("\n敲任意键从公共帐户中取钱,ESC键退出……\n");
  20. while (System.Console.ReadKey(true).Key !=ConsoleKey.Escape)
  21. {
  22. System.Console.WriteLine("");
  23. Thread One = new Thread(WithDrawMoney);
  24. Thread Two = new Thread(WithDrawMoney);
  25. Thread Three = new Thread(WithDrawMoney);
  26. //随机生成一个要提款的数额,最少100元,最高5000元
  27. Random ran = new Random();
  28. One.Start(ran.Next(100, 5000));
  29. Two.Start(ran.Next(100, 5000));
  30. Three.Start(ran.Next(100, 5000));
  31. //等三人取完钱
  32. One.Join();
  33. Two.Join();
  34. Three.Join();
  35. System.Console.WriteLine("公共账号剩余{0}元,ATM中可提现金:{1}", OneATM.QueryPublicAccount(),OneATM.QueryATMLeftAccount());
  36. }
  37. }
  38. //线程函数
  39. static void WithDrawMoney(object amount)
  40. {
  41. switch(OneATM.WithDraw((int)amount))
  42. {
  43. case WithDrawState.Succeed:
  44. System.Console.WriteLine("成功取出{0}元。",amount );
  45. break;
  46. case WithDrawState.ATMHasNotEnoughCash:
  47. System.Console.WriteLine("ATM中现金不足,无法支取{0}元。", amount);
  48. break ;
  49. case WithDrawState.AccountHasNotEnoughMoney:
  50. System.Console.WriteLine("帐户中没钱了!无法取出{0}元",amount);
  51. break ;
  52. }
  53. }
  54. }
  55. //自助取款机
  56. class ATM
  57. {
  58. private int PublicAcountLeftMoney;//帐户剩余的钱
  59. private int ATMLeftMoney;//提款机剩余的钱
  60. //同步信息号量
  61. private Mutex m = new Mutex();
  62. //取钱
  63. public WithDrawState WithDraw(int amount)
  64. {
  65. m.WaitOne();
  66. //公共帐号钱不够
  67. if (PublicAcountLeftMoney < amount)
  68. {
  69. m.ReleaseMutex();
  70. return WithDrawState.AccountHasNotEnoughMoney;
  71. }
  72. //ATM现金不够
  73. if (ATMLeftMoney < amount)
  74. {
  75. m.ReleaseMutex();
  76. return WithDrawState.ATMHasNotEnoughCash;
  77. }
  78. //用户可以提取现金
  79. ATMLeftMoney -= amount;
  80. PublicAcountLeftMoney -= amount;
  81. m.ReleaseMutex();
  82. return WithDrawState.Succeed;
  83. }
  84. //存钱
  85. public void Deposit(int amount)
  86. {
  87. m.WaitOne();
  88. PublicAcountLeftMoney += amount;
  89. m.ReleaseMutex();
  90. }
  91. /// <summary>
  92. /// 设置ATM的现金金额
  93. /// </summary>
  94. /// <param name="amount"></param>
  95. public void SetATMLeftMoney(int amount)
  96. {
  97. Interlocked.Exchange(ref ATMLeftMoney, amount);
  98. }
  99. //获取还剩余多少钱
  100. public int QueryPublicAccount()
  101. {
  102. return PublicAcountLeftMoney;
  103. }
  104. /// <summary>
  105. /// 查询ATM剩余多少钱
  106. /// </summary>
  107. /// <returns></returns>
  108. public int QueryATMLeftAccount()
  109. {
  110. return ATMLeftMoney;
  111. }
  112. }
  113. //取款状态
  114. public enum WithDrawState
  115. {
  116. Succeed,        //取钱成功
  117. AccountHasNotEnoughMoney, //账号中没钱了
  118. ATMHasNotEnoughCash  //ATM中没有足够的现金
  119. }
  120. }
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace UseATM
{
class Program
{
static ATM OneATM=new ATM(); //共享资源
static void Main(string[] args)
{
//向公共帐号存款2万 Console.Write("输入公司公共帐户的金额:");
int PublicAcountMoney =Convert.ToInt32(Console.ReadLine());
OneATM.Deposit(PublicAcountMoney); Console.Write("输入ATM中的现金额:");
int ATMLeftMoney = Convert.ToInt32(Console.ReadLine());
OneATM.SetATMLeftMoney(ATMLeftMoney); System.Console.WriteLine("\n敲任意键从公共帐户中取钱,ESC键退出……\n"); while (System.Console.ReadKey(true).Key !=ConsoleKey.Escape)
{
System.Console.WriteLine("");
Thread One = new Thread(WithDrawMoney);
Thread Two = new Thread(WithDrawMoney);
Thread Three = new Thread(WithDrawMoney); //随机生成一个要提款的数额,最少100元,最高5000元
Random ran = new Random();
One.Start(ran.Next(100, 5000));
Two.Start(ran.Next(100, 5000));
Three.Start(ran.Next(100, 5000)); //等三人取完钱
One.Join();
Two.Join();
Three.Join(); System.Console.WriteLine("公共账号剩余{0}元,ATM中可提现金:{1}", OneATM.QueryPublicAccount(),OneATM.QueryATMLeftAccount());
}
} //线程函数
static void WithDrawMoney(object amount)
{
switch(OneATM.WithDraw((int)amount))
{
case WithDrawState.Succeed:
System.Console.WriteLine("成功取出{0}元。",amount );
break;
case WithDrawState.ATMHasNotEnoughCash:
System.Console.WriteLine("ATM中现金不足,无法支取{0}元。", amount);
break ;
case WithDrawState.AccountHasNotEnoughMoney:
System.Console.WriteLine("帐户中没钱了!无法取出{0}元",amount);
break ;
}
}
} //自助取款机
class ATM
{
private int PublicAcountLeftMoney;//帐户剩余的钱
private int ATMLeftMoney;//提款机剩余的钱 //同步信息号量
private Mutex m = new Mutex(); //取钱
public WithDrawState WithDraw(int amount)
{
m.WaitOne();
//公共帐号钱不够
if (PublicAcountLeftMoney < amount)
{
m.ReleaseMutex();
return WithDrawState.AccountHasNotEnoughMoney;
}
//ATM现金不够
if (ATMLeftMoney < amount)
{
m.ReleaseMutex();
return WithDrawState.ATMHasNotEnoughCash;
}
//用户可以提取现金
ATMLeftMoney -= amount;
PublicAcountLeftMoney -= amount;
m.ReleaseMutex();
return WithDrawState.Succeed;
}
//存钱
public void Deposit(int amount)
{
m.WaitOne();
PublicAcountLeftMoney += amount;
m.ReleaseMutex();
} /// <summary>
/// 设置ATM的现金金额
/// </summary>
/// <param name="amount"></param>
public void SetATMLeftMoney(int amount)
{
Interlocked.Exchange(ref ATMLeftMoney, amount);
}
//获取还剩余多少钱
public int QueryPublicAccount()
{
return PublicAcountLeftMoney;
} /// <summary>
/// 查询ATM剩余多少钱
/// </summary>
/// <returns></returns>
public int QueryATMLeftAccount()
{
return ATMLeftMoney;
}
}
//取款状态
public enum WithDrawState
{
Succeed, //取钱成功
AccountHasNotEnoughMoney, //账号中没钱了
ATMHasNotEnoughCash //ATM中没有足够的现金
}
}

可能的运行结果:

  1. 输入公司公共帐户的金额:200000
  2. 输入ATM中的现金额:6000000
  3. 敲任意键从公共帐户中取钱,ESC键退出……
  4. 成功取出1249元。
  5. 成功取出643元。
  6. 成功取出4958元。
  7. 公共账号剩余193150元,ATM中可提现金:5993150
  8. 成功取出1168元。
  9. 成功取出3650元。
  10. 成功取出2707元。
  11. 公共账号剩余185625元,ATM中可提现金:5985625
  12. 成功取出3866元。
  13. 成功取出402元。
  14. 成功取出2397元。
  15. 公共账号剩余178960元,ATM中可提现金:5978960
  16. 成功取出4485元。
  17. 成功取出1701元。
  18. 成功取出3354元。
  19. 公共账号剩余169420元,ATM中可提现金:5969420
  1. 输入公司公共帐户的金额:200000
  2. 输入ATM中的现金额:6000000
  3. 敲任意键从公共帐户中取钱,ESC键退出……
  4. 成功取出1249元。
  5. 成功取出643元。
  6. 成功取出4958元。
  7. 公共账号剩余193150元,ATM中可提现金:5993150
  8. 成功取出1168元。
  9. 成功取出3650元。
  10. 成功取出2707元。
  11. 公共账号剩余185625元,ATM中可提现金:5985625
  12. 成功取出3866元。
  13. 成功取出402元。
  14. 成功取出2397元。
  15. 公共账号剩余178960元,ATM中可提现金:5978960
  16. 成功取出4485元。
  17. 成功取出1701元。
  18. 成功取出3354元。
  19. 公共账号剩余169420元,ATM中可提现金:5969420
输入公司公共帐户的金额:200000
输入ATM中的现金额:6000000 敲任意键从公共帐户中取钱,ESC键退出…… 成功取出1249元。
成功取出643元。
成功取出4958元。
公共账号剩余193150元,ATM中可提现金:5993150 成功取出1168元。
成功取出3650元。
成功取出2707元。
公共账号剩余185625元,ATM中可提现金:5985625 成功取出3866元。
成功取出402元。
成功取出2397元。
公共账号剩余178960元,ATM中可提现金:5978960 成功取出4485元。
成功取出1701元。
成功取出3354元。
公共账号剩余169420元,ATM中可提现金:5969420

Mustex与Monitor有一个很大的区别:

        Mutex可以用来同步属于不同应用程序或者进程的线程,而Monitor没有这个能力。

        为了说明这个区别,我们将生产者和消费者线程分别放在两个应用程序中,在两个应用程序中都各自创建一个同名的Mutex对象,并利用他们来对生产者和消费者线程同步:
生产者线程所在应用程序代码:
  1. // Mutex1.cs
  2. // Mutex1示例
  3. using System;
  4. using System.IO;
  5. using System.Threading;
  6. using System.Diagnostics;
  7. class Test
  8. {
  9. static void Main()
  10. {
  11. Test t = new Test();
  12. // 进行测试
  13. t.Go();
  14. }
  15. public void Go()
  16. {
  17. // 创建并启动线程
  18. Thread t1 = new Thread(new ThreadStart(Producer));
  19. t1.Name = "生产者线程";
  20. t1.Start();
  21. // 等待线程结束
  22. t1.Join();
  23. Console.WriteLine("按Enter键退出...");
  24. Console.Read();
  25. }
  26. // 生产者方法
  27. public void Producer()
  28. {
  29. Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);
  30. // 创建互斥体
  31. Mutex mutex = new Mutex(false, "CSharp_Mutex_test");
  32. // 启动消费者进程
  33. Process.Start("Mutex2.exe");
  34. for (int j = 0; j < 16; ++j)
  35. {
  36. try
  37. {
  38. // 进入互斥体
  39. mutex.WaitOne();
  40. FileStream fs = new FileStream(@"d:\text.txt", FileMode.OpenOrCreate, FileAccess.Write);
  41. StreamWriter sw = new StreamWriter(fs);
  42. // 构造字符串
  43. Random r = new Random();
  44. int bufSize = r.Next() % 64;
  45. char[] s = new char[bufSize];
  46. for (int i = 0; i < bufSize; ++i)
  47. {
  48. s[i] = (char)((int)'A' + r.Next() % 26);
  49. }
  50. string str = new string(s);
  51. // 将字符串写入文件
  52. sw.WriteLine(str);
  53. sw.Close();
  54. Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, str);
  55. }
  56. catch (System.Threading.ThreadInterruptedException)
  57. {
  58. Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
  59. break;
  60. }
  61. finally
  62. {
  63. // 退出互斥体
  64. mutex.ReleaseMutex();
  65. }
  66. // 休眠,将时间片让给消费者
  67. Thread.Sleep(1000);
  68. }
  69. // 关闭互斥体
  70. mutex.Close();
  71. Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
  72. }
  73. }
  1. // Mutex1.cs
  2. // Mutex1示例
  3. using System;
  4. using System.IO;
  5. using System.Threading;
  6. using System.Diagnostics;
  7. class Test
  8. {
  9. static void Main()
  10. {
  11. Test t = new Test();
  12. // 进行测试
  13. t.Go();
  14. }
  15. public void Go()
  16. {
  17. // 创建并启动线程
  18. Thread t1 = new Thread(new ThreadStart(Producer));
  19. t1.Name = "生产者线程";
  20. t1.Start();
  21. // 等待线程结束
  22. t1.Join();
  23. Console.WriteLine("按Enter键退出...");
  24. Console.Read();
  25. }
  26. // 生产者方法
  27. public void Producer()
  28. {
  29. Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);
  30. // 创建互斥体
  31. Mutex mutex = new Mutex(false, "CSharp_Mutex_test");
  32. // 启动消费者进程
  33. Process.Start("Mutex2.exe");
  34. for (int j = 0; j < 16; ++j)
  35. {
  36. try
  37. {
  38. // 进入互斥体
  39. mutex.WaitOne();
  40. FileStream fs = new FileStream(@"d:\text.txt", FileMode.OpenOrCreate, FileAccess.Write);
  41. StreamWriter sw = new StreamWriter(fs);
  42. // 构造字符串
  43. Random r = new Random();
  44. int bufSize = r.Next() % 64;
  45. char[] s = new char[bufSize];
  46. for (int i = 0; i < bufSize; ++i)
  47. {
  48. s[i] = (char)((int)'A' + r.Next() % 26);
  49. }
  50. string str = new string(s);
  51. // 将字符串写入文件
  52. sw.WriteLine(str);
  53. sw.Close();
  54. Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, str);
  55. }
  56. catch (System.Threading.ThreadInterruptedException)
  57. {
  58. Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
  59. break;
  60. }
  61. finally
  62. {
  63. // 退出互斥体
  64. mutex.ReleaseMutex();
  65. }
  66. // 休眠,将时间片让给消费者
  67. Thread.Sleep(1000);
  68. }
  69. // 关闭互斥体
  70. mutex.Close();
  71. Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
  72. }
  73. }
// Mutex1.cs
// Mutex1示例 using System;
using System.IO;
using System.Threading;
using System.Diagnostics; class Test
{
static void Main()
{
Test t = new Test();
// 进行测试
t.Go();
} public void Go()
{
// 创建并启动线程
Thread t1 = new Thread(new ThreadStart(Producer));
t1.Name = "生产者线程";
t1.Start(); // 等待线程结束
t1.Join(); Console.WriteLine("按Enter键退出...");
Console.Read();
} // 生产者方法
public void Producer()
{
Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name); // 创建互斥体
Mutex mutex = new Mutex(false, "CSharp_Mutex_test"); // 启动消费者进程
Process.Start("Mutex2.exe"); for (int j = 0; j < 16; ++j)
{
try
{
// 进入互斥体
mutex.WaitOne(); FileStream fs = new FileStream(@"d:\text.txt", FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
// 构造字符串
Random r = new Random();
int bufSize = r.Next() % 64;
char[] s = new char[bufSize];
for (int i = 0; i < bufSize; ++i)
{
s[i] = (char)((int)'A' + r.Next() % 26);
}
string str = new string(s);
// 将字符串写入文件
sw.WriteLine(str);
sw.Close(); Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, str);
}
catch (System.Threading.ThreadInterruptedException)
{
Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
break;
}
finally
{
// 退出互斥体
mutex.ReleaseMutex();
} // 休眠,将时间片让给消费者
Thread.Sleep(1000);
} // 关闭互斥体
mutex.Close();
Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
}
}

消费者线程所在应用程序代码:

  1. // Mutex2.cs
  2. // Mutex2示例
  3. using System;
  4. using System.IO;
  5. using System.Threading;
  6. class Test
  7. {
  8. static void Main()
  9. {
  10. Test t = new Test();
  11. // 进行测试
  12. t.Go();
  13. }
  14. public void Go()
  15. {
  16. // 创建并启动线程
  17. Thread t2 = new Thread(new ThreadStart(Consumer));
  18. t2.Name = "消费者线程";
  19. t2.Start();
  20. // 等待线程结束
  21. t2.Join();
  22. Console.WriteLine("按Enter键退出...");
  23. Console.Read();
  24. }
  25. // 消费者方法
  26. public void Consumer()
  27. {
  28. Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);
  29. // 创建互斥体
  30. Mutex mutex = new Mutex(false, "CSharp_Mutex_test");
  31. for (int j = 0; j < 16; ++j)
  32. {
  33. try
  34. {
  35. // 进入互斥体
  36. mutex.WaitOne();
  37. StreamReader sr = new StreamReader(@"d:\text.txt");
  38. string s = sr.ReadLine();
  39. sr.Close();
  40. // 显示字符串的值
  41. Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, s);
  42. }
  43. catch (System.Threading.ThreadInterruptedException)
  44. {
  45. Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
  46. break;
  47. }
  48. finally
  49. {
  50. // 退出互斥体
  51. mutex.ReleaseMutex();
  52. }
  53. // 休眠,将时间片让给消费者
  54. Thread.Sleep(1000);
  55. }
  56. // 关闭互斥体
  57. mutex.Close();
  58. Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
  59. }
  60. }
  1. // Mutex2.cs
  2. // Mutex2示例
  3. using System;
  4. using System.IO;
  5. using System.Threading;
  6. class Test
  7. {
  8. static void Main()
  9. {
  10. Test t = new Test();
  11. // 进行测试
  12. t.Go();
  13. }
  14. public void Go()
  15. {
  16. // 创建并启动线程
  17. Thread t2 = new Thread(new ThreadStart(Consumer));
  18. t2.Name = "消费者线程";
  19. t2.Start();
  20. // 等待线程结束
  21. t2.Join();
  22. Console.WriteLine("按Enter键退出...");
  23. Console.Read();
  24. }
  25. // 消费者方法
  26. public void Consumer()
  27. {
  28. Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);
  29. // 创建互斥体
  30. Mutex mutex = new Mutex(false, "CSharp_Mutex_test");
  31. for (int j = 0; j < 16; ++j)
  32. {
  33. try
  34. {
  35. // 进入互斥体
  36. mutex.WaitOne();
  37. StreamReader sr = new StreamReader(@"d:\text.txt");
  38. string s = sr.ReadLine();
  39. sr.Close();
  40. // 显示字符串的值
  41. Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, s);
  42. }
  43. catch (System.Threading.ThreadInterruptedException)
  44. {
  45. Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
  46. break;
  47. }
  48. finally
  49. {
  50. // 退出互斥体
  51. mutex.ReleaseMutex();
  52. }
  53. // 休眠,将时间片让给消费者
  54. Thread.Sleep(1000);
  55. }
  56. // 关闭互斥体
  57. mutex.Close();
  58. Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
  59. }
  60. }
// Mutex2.cs
// Mutex2示例 using System;
using System.IO;
using System.Threading; class Test
{
static void Main()
{
Test t = new Test();
// 进行测试
t.Go();
} public void Go()
{
// 创建并启动线程
Thread t2 = new Thread(new ThreadStart(Consumer));
t2.Name = "消费者线程";
t2.Start(); // 等待线程结束
t2.Join(); Console.WriteLine("按Enter键退出...");
Console.Read();
} // 消费者方法
public void Consumer()
{
Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name); // 创建互斥体
Mutex mutex = new Mutex(false, "CSharp_Mutex_test"); for (int j = 0; j < 16; ++j)
{
try
{
// 进入互斥体
mutex.WaitOne(); StreamReader sr = new StreamReader(@"d:\text.txt");
string s = sr.ReadLine();
sr.Close(); // 显示字符串的值
Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, s);
}
catch (System.Threading.ThreadInterruptedException)
{
Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);
break;
}
finally
{
// 退出互斥体
mutex.ReleaseMutex();
} // 休眠,将时间片让给消费者
Thread.Sleep(1000);
} // 关闭互斥体
mutex.Close();
Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);
}
}

我们分别编译这两个文件,然后运行Mutex1,他会在另一个窗口中启动Mutex2,可能的结果如下:

 
 

6、Semaphore

 
        Semaphore可以限制可同时访问某一资源或资源池的线程数。
        Semaphore类在内部维护一个计数器,当一个线程调用Semaphore对象的Wait系列方法时,此计数器减一,只要计数器还是一个正数,线程就不会阻塞。当计数器减到0时,再调用Semaphore对象Wait系列方法的线程将被阻塞,直到有线程调用Semaphore对象的Release()方法增加计数器值时,才有可能解除阻塞状态。
 
示例说明:
图书馆都配备有若干台公用计算机供读者查询信息,当某日读者比较多时,必须排队等候。UseLibraryComputer实例用多线程模拟了多人使用多台计算机的过程
 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace UseLibraryComputer
  6. {
  7. class Program
  8. {
  9. //图书馆拥有的公用计算机
  10. private const int ComputerNum = 3;
  11. private static Computer[] LibraryComputers;
  12. //同步信号量
  13. public static  Semaphore sp = new Semaphore( ComputerNum, ComputerNum);
  14. static void Main(string[] args)
  15. {
  16. //图书馆拥有ComputerNum台电脑
  17. LibraryComputers = new Computer[ComputerNum];
  18. for (int i = 0; i <ComputerNum; i++)
  19. LibraryComputers[i] = new Computer("Computer"+(i+1).ToString());
  20. int peopleNum = 0;
  21. Random ran=new Random();
  22. Thread user;
  23. System.Console.WriteLine("敲任意键模拟一批批的人排队使用{0}台计算机,ESC键结束模拟……" ,ComputerNum);
  24. //每次创建若干个线程,模拟人排队使用计算机
  25. while (System.Console.ReadKey().Key  != ConsoleKey.Escape)
  26. {
  27. peopleNum = ran.Next(0, 10);
  28. System.Console.WriteLine("\n有{0}人在等待使用计算机。",peopleNum );
  29. for (int i = 1; i <= peopleNum; i++)
  30. {
  31. user = new Thread(UseComputer);
  32. user.Start("User" + i.ToString());
  33. }
  34. }
  35. }
  36. //线程函数
  37. static void UseComputer(Object UserName)
  38. {
  39. sp.WaitOne();//等待计算机可用
  40. //查找可用的计算机
  41. Computer cp=null;
  42. for (int i = 0; i < ComputerNum; i++)
  43. if (LibraryComputers[i].IsOccupied == false)
  44. {
  45. cp = LibraryComputers[i];
  46. break;
  47. }
  48. //使用计算机工作
  49. cp.Use(UserName.ToString());
  50. //不再使用计算机,让出来给其他人使用
  51. sp.Release();
  52. }
  53. }
  54. class Computer
  55. {
  56. public readonly string ComputerName = "";
  57. public Computer(string Name)
  58. {
  59. ComputerName = Name;
  60. }
  61. //是否被占用
  62. public  bool IsOccupied = false;
  63. //人在使用计算机
  64. public  void Use(String userName)
  65. {
  66. System.Console.WriteLine("{0}开始使用计算机{1}", userName,ComputerName);
  67. IsOccupied = true;
  68. Thread.Sleep(new Random().Next(1, 2000)); //随机休眠,以模拟人使用计算机
  69. System.Console.WriteLine("{0}结束使用计算机{1}", userName,ComputerName);
  70. IsOccupied = false;
  71. }
  72. }
  73. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Threading;
  5. namespace UseLibraryComputer
  6. {
  7. class Program
  8. {
  9. //图书馆拥有的公用计算机
  10. private const int ComputerNum = 3;
  11. private static Computer[] LibraryComputers;
  12. //同步信号量
  13. public static  Semaphore sp = new Semaphore( ComputerNum, ComputerNum);
  14. static void Main(string[] args)
  15. {
  16. //图书馆拥有ComputerNum台电脑
  17. LibraryComputers = new Computer[ComputerNum];
  18. for (int i = 0; i <ComputerNum; i++)
  19. LibraryComputers[i] = new Computer("Computer"+(i+1).ToString());
  20. int peopleNum = 0;
  21. Random ran=new Random();
  22. Thread user;
  23. System.Console.WriteLine("敲任意键模拟一批批的人排队使用{0}台计算机,ESC键结束模拟……" ,ComputerNum);
  24. //每次创建若干个线程,模拟人排队使用计算机
  25. while (System.Console.ReadKey().Key  != ConsoleKey.Escape)
  26. {
  27. peopleNum = ran.Next(0, 10);
  28. System.Console.WriteLine("\n有{0}人在等待使用计算机。",peopleNum );
  29. for (int i = 1; i <= peopleNum; i++)
  30. {
  31. user = new Thread(UseComputer);
  32. user.Start("User" + i.ToString());
  33. }
  34. }
  35. }
  36. //线程函数
  37. static void UseComputer(Object UserName)
  38. {
  39. sp.WaitOne();//等待计算机可用
  40. //查找可用的计算机
  41. Computer cp=null;
  42. for (int i = 0; i < ComputerNum; i++)
  43. if (LibraryComputers[i].IsOccupied == false)
  44. {
  45. cp = LibraryComputers[i];
  46. break;
  47. }
  48. //使用计算机工作
  49. cp.Use(UserName.ToString());
  50. //不再使用计算机,让出来给其他人使用
  51. sp.Release();
  52. }
  53. }
  54. class Computer
  55. {
  56. public readonly string ComputerName = "";
  57. public Computer(string Name)
  58. {
  59. ComputerName = Name;
  60. }
  61. //是否被占用
  62. public  bool IsOccupied = false;
  63. //人在使用计算机
  64. public  void Use(String userName)
  65. {
  66. System.Console.WriteLine("{0}开始使用计算机{1}", userName,ComputerName);
  67. IsOccupied = true;
  68. Thread.Sleep(new Random().Next(1, 2000)); //随机休眠,以模拟人使用计算机
  69. System.Console.WriteLine("{0}结束使用计算机{1}", userName,ComputerName);
  70. IsOccupied = false;
  71. }
  72. }
  73. }
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace UseLibraryComputer
{
class Program
{
//图书馆拥有的公用计算机
private const int ComputerNum = 3;
private static Computer[] LibraryComputers;
//同步信号量
public static Semaphore sp = new Semaphore( ComputerNum, ComputerNum); static void Main(string[] args)
{
//图书馆拥有ComputerNum台电脑
LibraryComputers = new Computer[ComputerNum];
for (int i = 0; i <ComputerNum; i++)
LibraryComputers[i] = new Computer("Computer"+(i+1).ToString());
int peopleNum = 0;
Random ran=new Random();
Thread user;
System.Console.WriteLine("敲任意键模拟一批批的人排队使用{0}台计算机,ESC键结束模拟……" ,ComputerNum);
//每次创建若干个线程,模拟人排队使用计算机
while (System.Console.ReadKey().Key != ConsoleKey.Escape)
{
peopleNum = ran.Next(0, 10);
System.Console.WriteLine("\n有{0}人在等待使用计算机。",peopleNum ); for (int i = 1; i <= peopleNum; i++)
{
user = new Thread(UseComputer);
user.Start("User" + i.ToString());
}
}
} //线程函数
static void UseComputer(Object UserName)
{
sp.WaitOne();//等待计算机可用 //查找可用的计算机
Computer cp=null;
for (int i = 0; i < ComputerNum; i++)
if (LibraryComputers[i].IsOccupied == false)
{
cp = LibraryComputers[i];
break;
}
//使用计算机工作
cp.Use(UserName.ToString()); //不再使用计算机,让出来给其他人使用
sp.Release();
}
} class Computer
{
public readonly string ComputerName = "";
public Computer(string Name)
{
ComputerName = Name;
}
//是否被占用
public bool IsOccupied = false;
//人在使用计算机
public void Use(String userName)
{
System.Console.WriteLine("{0}开始使用计算机{1}", userName,ComputerName);
IsOccupied = true;
Thread.Sleep(new Random().Next(1, 2000)); //随机休眠,以模拟人使用计算机
System.Console.WriteLine("{0}结束使用计算机{1}", userName,ComputerName);
IsOccupied = false;
}
}
}

可能的运行结果:

多线程同步与并发访问共享资源工具—Lock、Monitor、Mutex、Semaphore的更多相关文章

  1. C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEvent

    看下组织结构: System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.Mutex S ...

  2. 线程系列08,实现线程锁的各种方式,使用lock,Montor,Mutex,Semaphore以及线程死锁

    当涉及到多线程共享数据,需要数据同步的时候,就可以考虑使用线程锁了.本篇体验线程锁的各种用法以及线程死锁.主要包括: ※ 使用lock处理数据同步※ 使用Monitor.Enter和Monitor.E ...

  3. C# 多线程(lock,Monitor,Mutex,同步事件和等待句柄)

    本篇从 Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler 的类关系图开始,希望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而 ...

  4. 【转】多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄(上)

    本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开始,希望通过 本篇的介绍能对常见的线程同步方法有一个整体的认识,而对 ...

  5. Java多线程同步集合--并发库高级应用

    一.阻塞队列1.在多线程领域,所谓阻塞,在某些情况下会挂起线程,一旦条件满足,被挂起的线程又会自动被唤醒2.ArrayBlockingQueue(效率高)和LinkedBlockingQueue是两个 ...

  6. [Java Concurrent] 并发访问共享资源的简单案例

    EvenGenerator 是一个偶数生成器,每调用一个 next() 就会加 2 并返回叠加后结果.在本案例中,充当被共享的资源. EvenChecker 实现了 Runnable 接口,可以启动新 ...

  7. java 多线程 同步 观察者 并发集合的一个例子

    //第一版 package com.hra.riskprice; import com.hra.riskprice.SysEnum.Factor_Type; import org.springfram ...

  8. C#多线程:深入了解线程同步lock,Monitor,Mutex,同步事件和等待句柄(中)

    本篇继续介绍WaitHandler类及其子类 Mutex,ManualResetEvent,AutoResetEvent的用法..NET中线程同步的方式多的让人看了眼花缭乱,究竟该怎么去理解呢?其实, ...

  9. java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)

    本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...

随机推荐

  1. angularjs 常用方法

    一 angular的copy和extend 1.angular.extend() angular.extend():依次将第二个参数及后续的参数的第一层属性(不管是简单的属性还是对象)拷贝,赋给第一个 ...

  2. 在Docker中安装和部署MongoDB集群

    此文已由作者袁欢授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 在Docker中安装mongodb 采用的mongodb镜像:https://registry.hub.doc ...

  3. Python调用matplotlib实现交互式数据可视化图表案例

    交互式的数据可视化图表是 New IT 新技术的一个应用方向,在过去,用户要在网页上查看数据,基本的实现方式就是在页面上显示一个表格出来,的而且确,用表格的方式来展示数据,显示的数据量会比较大,但是, ...

  4. Spring是什么?优点是什么?

    大部分项目都少不了Spring的身影,为什么大家对他如此青睐,而且对他的追捧丝毫没有减退之势呢 Spring是什么: Spring是一个轻量级的DI和AOP容器框架. 说它轻量级有一大部分原因是相对与 ...

  5. Jmeter+Ant+Jenkins持续集成方案改进

    关于Jmeter+Ant+Jenkins如何搭建持续集成环境,网上资料一大把,就不多说了,本文主要谈一下期间的问题及扩展该持续集成方案. 其实核心的流程不复杂,Jenkins管理构建项目,Ant配置脚 ...

  6. Loadrunner安装使用入门

    1. Loadrunner11安装指南 1)支持的Windows环境 2)安装 开始安装时会提示需要以下软件: .NET Framework v3.5 SP1 Microsoft WSE 2.0 SP ...

  7. python爬虫beautifulsoup4系列1

    前言 以博客园为例,爬取我的博客上首页的发布时间.标题.摘要,本篇先小试牛刀,先了解下它的强大之处,后面讲beautifulsoup4的详细功能. 一.安装 1.打开cmd用pip在线安装beauti ...

  8. Netty源码分析第2章(NioEventLoop)---->第5节: 优化selector

    Netty源码分析第二章: NioEventLoop   第五节: 优化selector 在剖析selector轮询之前, 我们先讲解一下selector的创建过程 回顾之前的小节, 在创建NioEv ...

  9. Flink架构分析之资源分配

    Task Slot     Flink中每个真正执行任务的TaskManager都是一个JVM进程,其在多线程环境中执行一个或者多个子任务.为了控制一个JVM同时能运行的任务数量,flink引入了ta ...

  10. hostname命令详解

    基础命令学习目录首页 原文链接:https://idc.wanyunshuju.com/cym/68.html Linux操作系统的hostname是一个kernel变量,可以通过hostname命令 ...