3.1 理解同步块和同步块索引
同步块是.NET中解决对象同步问题的基本机制,该机制为每个堆内的对象(即引用类型对象实例)分配一个同步索引,该索引中只保存一个表明数组内索引的整数。具体过程是:.NET在加载时就会新建一个同步块数组,当某个对象需要被同步时,.NET会为其分配一个同步块,并且把该同步块在同步块数组中的索引加入该对象的同步块索引中。下图展现了这一机制的实现:
同步块机制包含以下几点:
① 在.NET被加载时初始化同步块数组;
② 每一个被分配在堆上的对象都会包含两个额外的字段,其中一个存储类型指针,而另外一个就是同步块索引,初始时被赋值为-1;
③ 当一个线程试图使用该对象进入同步时,会检查该对象的同步索引:
如果同步索引为负数,则会在同步块数组中新建一个同步块,并且将该同步块的索引值写入该对象的同步索引中;
如果同步索引不为负数,则找到该对象的同步块并检查是否有其他线程在使用该同步块,如果有则进入等待状态,如果没有则申明使用该同步块;
④ 当一个对象退出同步时,该对象的同步索引被修改为-1,并且相应的同步块数组中的同步块被视为不再使用。
3.2 C#中的lock关键字有啥作用?
lock关键字可能是我们在遇到线程同步的需求时最常用的方式,但lock只是一个语法糖,为什么这么说呢,下面慢慢道来。
(1)lock的等效代码其实是Monitor类的Enter和Exit两个方法
private object locker = new object(); public void Work() { lock (locker) { // 做一些需要线程同步的工作 } }
事实上,lock关键字时一个方便程序员使用的语法糖,它等效于安全地使用System.Threading.Monitor类型,它直接等效于下面的代码:
private object locker = new object(); public void Work() { // 避免直接使用私有成员locker(直接使用有可能会导致线程不安全) object temp = locker; Monitor.Enter(temp); try { // 做一些需要线程同步的工作 } finally { Monitor.Exit(temp); } }
(2)System.Threading.Monitor类型的作用和使用
Monitor类型的Enter和Exit方法用来实现进入和退出对象的同步,当Enter方法被调用时,对象的同步索引将被检查,并且.NET将负责一系列的后续工作来保证对象访问时的线程同步,而Exit方法的调用则保证了当前线程释放该对象的同步块。
下面的代码示例演示了如何使用lock关键字来实现线程同步:
class Program { static void Main(string[] args) { // 多线程测试静态方法的同步 Console.WriteLine("开始测试静态方法的同步:"); for (int i = 0; i < 5; i++) { Thread thread = new Thread(Lock.StaticIncrement); thread.Start(); } // 这里等待线程执行结束 Thread.Sleep(5 * 1000); Console.WriteLine("-------------------------------"); // 多线程测试实例方法的同步 Console.WriteLine("开始测试实例方法的同步:"); Lock l = new Lock(); for (int i = 0; i < 6; i++) { Thread thread = new Thread(l.InstanceIncrement); thread.Start(); } Console.ReadKey(); } } public class Lock { // 静态方法同步锁 private static object staticLocker = new object(); // 实例方法同步锁 private object instanceLocker = new object(); // 成员变量 private static int staticNumber = 0; private int instanceNumber = 0; // 测试静态方法的同步 public static void StaticIncrement(object state) { lock (staticLocker) { Console.WriteLine("当前线程ID:{0}", Thread.CurrentThread.ManagedThreadId.ToString()); Console.WriteLine("staticNumber的值为:{0}", staticNumber.ToString()); // 这里可以制造线程并行执行的机会,来检查同步的功能 Thread.Sleep(200); staticNumber++; Console.WriteLine("staticNumber自增后为:{0}", staticNumber.ToString()); } } // 测试实例方法的同步 public void InstanceIncrement(object state) { lock (instanceLocker) { Console.WriteLine("当前线程ID:{0}",Thread.CurrentThread.ManagedThreadId.ToString()); Console.WriteLine("instanceNumber的值为:{0}", instanceNumber.ToString()); // 这里可以制造线程并行执行的机会,来检查同步的功能 Thread.Sleep(200); instanceNumber++; Console.WriteLine("instanceNumber自增后为:{0}", instanceNumber.ToString()); } } }