3.1 理解同步块和同步块索引

  同步块是.NET中解决对象同步问题的基本机制,该机制为每个堆内的对象(即引用类型对象实例)分配一个同步索引,该索引中只保存一个表明数组内索引的整数。具体过程是:.NET在加载时就会新建一个同步块数组,当某个对象需要被同步时,.NET会为其分配一个同步块,并且把该同步块在同步块数组中的索引加入该对象的同步块索引中。下图展现了这一机制的实现:

C# 多线程编程中的线程同步

  同步块机制包含以下几点:

  ① 在.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());
            }
        }
    }
View Code

相关文章: