“线程同步”的含义

 

当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如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. Spring学习----- Spring配置文件xml文档的schema约束

    1.配置文件示例. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="htt ...

  2. Sqlite数据库sqlite3命令小记

    SQLite库包含一个名字叫做sqlite3的命令行,它可以让用户手工输入并执行面向SQLite数据库的SQL命令.本文档提供一个样使用sqlite3的简要说明. 开始 启动sqlite3程序,仅仅需 ...

  3. CentOS 下 SonarQube 6.7 的下载、配置、问题排查

    CentOS 下 SonarQube 6.7 的下载.配置.问题排查 系统: CentOS 7 x86_64 SonarQube 版本: 6.7.3 Java 版本: 1.8.0_171 MySQL ...

  4. UnityEditor扩展-Shader浏览器

    1. 用途 用于浏览项目所有Shader被使用的情况 2. 界面说明 Ignore Directory:添加不搜索的文件夹,不添加默认搜索全部 Find按钮:开始搜索 Used Shaders:已被使 ...

  5. [Codeforces-888C] - K-Dominant Character

    C. K-Dominant Character time limit per test 2 seconds memory limit per test 256 megabytes input stan ...

  6. Datawhale MySQL 训练营 Task4 表联结

    学习内容 MySQL别名 列别名,将查询或者筛选出来列用AS 命名,如果有空格则需要引号 '' SELECT xxx AS xxxx FROM WHERE GROUP BY HAVING 表别名, 把 ...

  7. 【CentOS 7】scp示例

    1,从远端拷贝到本地 /tmp路径 root@raspberrypi:/download/api_weather# scp root@123.207.xxx.xxx:/xxx/* /tmp 2,从本地 ...

  8. linux 安装配置kafka脚本

    安装脚本 #!/bin/bash # auto install kafka echo "========= Start to install kafka ==============&quo ...

  9. python之模块_随手记录的模块

    目录 1.StringIO模块 2.string模块 3.pprint模块 4.struct模块 5.uuid模块 6.itertools 7.prettytable 1.StringIO (1)使用 ...

  10. js中if else switch 条件判断的替代方法

    function condition(test){ return({ cat :function(){console.log('cat');}, dog :function(){console.log ...