引用类型的对象是分配在堆上的,必然会包含同步索引,也可以分配同步块,所以原则上可以在对象的方法内对自身进行同步。而事实上,这样的代码也确实能有效地保证线程同步。But,这样的代码健壮性存在一定问题。
(1)lock(this)
回顾lock(this)的设计,就可以看出问题来:this代表了执行代码的当前对象,可以预见该对象可以被任何使用者访问,这就导致了不仅对象内部的代码在争用同步块,连类型的使用者也可以有意无意地进入到争用的队伍中→这显然不符合设计意图。
下面通过一个代码示例展示了一个恶意的使用者是如何导致类型死锁的:
class Program { static void Main(string[] args) { Console.WriteLine(); SynchroThis st = new SynchroThis(); // 模拟恶意的使用者 Monitor.Enter(st); // 正常的使用者会收到恶意使用者的影响 // 下面的代码完全正确,但却被死锁 Thread thread = new Thread(st.Work); thread.Start(); thread.Join(); // 程序不会执行到这里 Console.WriteLine(); Console.ReadKey(); } } public class SynchroThis { private int number = 0; public void Work(object state) { lock (this) { Console.WriteLine(, number.ToString()); number++; // 模拟做了其他工作 Thread.Sleep(200); Console.WriteLine(, number.ToString()); } } }
View Code运行这个示例,我们发现程序完全被死锁,这是因为一个恶意的使用者在使用了同步块之后却没有对其进行释放,导致了SynchroThis类型的方法被组织。
(2)lock(typeof(类型名))
这样的设计有时候会被用来在静态方法中实现线程同步,因为静态方法的访问需要通过类型来进行,但它也和lock(this)一样,缺乏健壮性。下面展示了常见的错误使用代码示例:
class Program { static void Main(string[] args) { Console.WriteLine(); SynchroThis st = new SynchroThis(); // 模拟恶意的使用者 Monitor.Enter(typeof(SynchroThis)); // 正常的使用者会收到恶意使用者的影响 // 下面的代码完全正确,但却被死锁 Thread thread = new Thread(SynchroThis.Work); thread.Start(); thread.Join(); // 程序不会执行到这里 Console.WriteLine(); Console.ReadKey(); } } public class SynchroThis { number = 0; Work(object state) { lock (typeof(SynchroThis)) { Console.WriteLine(, number.ToString()); number++; // 模拟做了其他工作 Thread.Sleep(200); Console.WriteLine(, number.ToString()); } } }
View Code可以发现,当一个恶意的使用者对type对象进行同步时,也会造成所有的使用者被死锁。
PS:应该完全避免使用this对象和当前类型对象作为同步对象,而应该在类型中定义私有的同步对象,同时应该使用lock而不是Monitor类型,这样可以有效地减少同步块不被释放的情况。
3.5 互斥体是个什么鬼?Mutex和Monitor两个类型的功能有啥区别?(1)什么是互斥体?
在操作系统中,互斥体(Mutex)是指某些代码片段在任意时间内只允许一个线程进入。例如,正在进行一盘棋,任意时刻只允许一个棋手往棋盘上落子,这和线程同步的概念基本一致。
(2).NET中的互斥体
Mutex类是.NET中为我们封装的一个互斥体类型,和Mutex类似的还有Semaphore(信号量)等类型。下面的示例代码展示了Mutext类型的使用:
class Program { ; 这个互斥体保证所有的进程都能得到同步 Mutex mutex = ); static void Main(string[] args) { //留出时间来启动其他进程 Thread.Sleep(3000); DoWork(); mutex.Close(); Console.ReadKey(); } 往文件里写连续的内容 DoWork() { long d1 = DateTime.Now.Ticks; mutex.WaitOne(); long d2 = DateTime.Now.Ticks; Console.WriteLine(, (d2 - d1).ToString(), Process.GetCurrentProcess().Id.ToString()); try { if (!File.Exists(testFile)) { FileStream fs = File.Create(testFile); fs.Dispose(); } for (int i = 0; i < 5; i++) { // 每次都保证文件被关闭再重新打开 (FileStream fs = File.Open(testFile, FileMode.Append)) { + Process.GetCurrentProcess().Id.ToString() + + i.ToString() + ; Byte[] data = Encoding.Default.GetBytes(content); fs.Write(data, 0, data.Length); } // 模拟做了其他工作 Thread.Sleep(300); } } finally { mutex.ReleaseMutex(); } } }
View Code模拟多个用户,执行上述代码,下图就是在我的计算机上的执行结果:
现在打开C盘目录下的TestMutext.txt文件,将看到如下图所示的结果:
(3)Mutex和Monitor的区别
这两者虽然都用来进行同步的功能,但实现方法不同,其最显著的两个差别如下:
① Mutex使用的是操作系统的内核对象,而Monitor类型的同步机制则完全在.NET框架之下实现,这就导致了Mutext类型的效率要比Monitor类型要低很多;
② Monitor类型只能同步同一应用程序域中的线程,而Mutex类型却可以跨越应用程序域和进程。
参考资料(1)朱毅,《进入IT企业必读的200个.NET面试题》
(2)张子阳,《.NET之美:.NET关键技术深入解析》
(3)王涛,《你必须知道的.NET》
作者:周旭龙
出处:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。