《C#多线程编程实战》1.10 lock关键字

lock关键字是锁定资源用的。

书上的代码解释很好。

   /// <summary>
    /// 抽象类 加减法
    /// </summary>
     abstract class CounterBase
    {
        /// <summary>
        ///抽象 加法 方法
        /// </summary>
        public abstract void Increment();

        /// <summary>
        /// 抽象 减法 方法
        /// </summary>
        public abstract void Decrement();
    }
    /// <summary>
    /// 不使用lock关键字的实现抽象类Counter
    /// </summary>
    class Counter : CounterBase
    {
        public int Count { get; private set; }
        /// <summary>
        /// 非Lock关键字减法
        /// </summary>
        public override void Decrement()
        {
            Count--;
        }
        /// <summary>
        /// 非lock关键字加法
        /// </summary>
        public override void Increment()
        {
            Count++;
        }
    }
    /// <summary>
    /// 使用Lock关键字的类
    /// </summary>
    class CounterWidthLock : CounterBase
    {
        /// <summary>
        /// 判断是否锁定资源
        /// </summary>
        public readonly object _syncRoot = new object();

        public int Count { get; private set; }

        /// <summary>
        /// Lock关键字减法
        /// </summary>
        public override void Decrement()
        {
            lock (_syncRoot)//lock关键字,锁定资源
            {
                Count--;
            }
        }
        /// <summary>
        /// lock关键字加法
        /// </summary>
        public override void Increment()
        {
            lock (_syncRoot)//lock关键字,锁定资源
            {
                Count++;
            }
        }
    }
    /// <summary>
    /// 测试主类
    /// </summary>
    class Program
    {
      /// <summary>
      /// 测试主程序入口
      /// </summary>
      /// <param name="args"></param>
        static void Main(string[] args)
        {
            Console.WriteLine("不使用Lock关键字 Counter类");

            var NotLockClass = new Counter();

            var t1 = new Thread(() =>TestCounter(NotLockClass));

            var t2 = new Thread(() => TestCounter(NotLockClass));

            var t3= new Thread(() => TestCounter(NotLockClass));

            t1.Start();

            t2.Start();

            t3.Start();

            t1.Join();

            t2.Join();

            t3.Join();

            Console.WriteLine($"最终输出结果是{NotLockClass.Count}");

            Console.ReadKey();
        }
        static void TestCounter(CounterBase counterBase)
        {
            for(int i=0;i<10000;i++)
            {
                counterBase.Increment();//加法

                counterBase.Decrement();//减法
            }
        }
    }

上面这一部分是,未使用lock关键字的代码。

结果也如同书上说count是未定。多次启动程序 结果也是不一样的。正确的结果应该是0,加一次见一次正好分别是5K次。

仔细观察代码,得到这样子的结果也是肯定的。

首先:

           var NotLockClass = new Counter();

            var t1 = new Thread(() =>TestCounter(NotLockClass));

            var t2 = new Thread(() => TestCounter(NotLockClass));

            var t3= new Thread(() => TestCounter(NotLockClass));

这个部分,是三个实例的Thread共享了一个Counter的实例化对象。

那么,要肯定的是这个Counter是一个引用类型

那么引用类型发挥在哪里呢?

            t1.Start();

            t2.Start();

            t3.Start();

            t1.Join();

            t2.Join();

            t3.Join();    

这里。

首先是T1 T2 T3三个线程启动。

然后是等待线程完成。

让结果变得不固定的原因就在这里。

我们先把方法TestCounter改造一下

   static void TestCounter(CounterBase counterBase)
        {
            for(int i=0;i<10000;i++)
            {
                counterBase.Increment();//加法
                Console.WriteLine($"加法启动次序{i}");
                counterBase.Decrement();//减法
                Console.WriteLine($"减法启动次序{i}");
            }
        }

加入一下两个控制台输出语句。

然后启动程序。

 

 会发现有很多重复的顺序。而这个就是资源抢夺。

为了更清楚的看执行的过程,我们再来改造一下。

 static void TestCounter(Counter counter)
        {
            for(int i=0;i<10000;i++)
            {
                counter.Increment();//加法
                Console.WriteLine($"加法启动次序{i},当前结果是{counter.Count}");
                counter.Decrement();//减法
                Console.WriteLine($"减法启动次序{i},当前结果是{counter.Count}");
            }
        }

之后在启动

 

 资源抢夺还是很严重的。 不过你们有没有发现。方法改造之后的结果和正确结果很相近了。不是-1,就是-2.这是一个很有意思的现象。

如果你将代码改造成:

class Counter : CounterBase
    {
        public int Count { get; private set; }
        /// <summary>
        /// 非Lock关键字减法
        /// </summary>
        public override void Decrement()
        {
            Console.WriteLine($"减法 当前{Count}");
            Count--;
        }
        /// <summary>
        /// 非lock关键字加法
        /// </summary>
        public override void Increment()
        {
            Console.WriteLine($"加法 当前{Count}");
            Count++;
        }
    }

结果:

 

竟然会出现正确答案,而且几率很高。得到-1这个答案我运行了很多次。-2更是看运气。

为什么会出现这个现象。我觉得是可以程序的方法可运行时间有一定关系吧。时间可能是多了一下。资源调度上面可能会分配。也可能是线程的原因。这个问题真的很有意思。

不过还是先放放。

 

 我们依旧是得到未使用lock关键字,资源抢夺很严重!

那么我们来看看是使用lock关键字的部分。

改造代码:

 static void Main(string[] args)
        {
            Console.WriteLine("使用Lock关键字 CounterWidthLock类");

            var UseLockClass = new CounterWidthLock();

            var t1 = new Thread(() =>TestCounter(UseLockClass));

            var t2 = new Thread(() => TestCounter(UseLockClass));

            var t3= new Thread(() => TestCounter(UseLockClass));

            t1.Start();

            t2.Start();

            t3.Start();

            t1.Join();

            t2.Join();

            t3.Join();

            Console.WriteLine($"最终输出结果是{UseLockClass.Count}");

            Console.ReadKey();
        }

 

 

肯定是正确答案。

 我们改造一下代码:和上一样:

 static void TestCounter(CounterBase counterBase)
        {
            for(int i=0;i<10000;i++)
            {
                counterBase.Increment();//加法
                Console.WriteLine($"加法启动次序{i}");
                counterBase.Decrement();//减法
                Console.WriteLine($"减法启动次序{i}");

            }
        }

看一下是否会出现资源抢夺

其实还是会出现的。

但是为什么结果会是正确的呢?

我们再来改造一下:

 static void TestCounter(CounterWidthLock counterwidthlock)
        {
            for(int i=0;i<10000;i++)
            {
                counterwidthlock.Increment();//加法
                Console.WriteLine($"加法启动次序{i},当前的结果是{counterwidthlock.Count}");
                counterwidthlock.Decrement();//减法
                Console.WriteLine($"减法启动次序{i},当前的结果是{counterwidthlock.Count}");

            }
        }

 

 

很明显,执行的顺序还是混乱。但是为什么结果却是正确呢?

我们来正式的讲讲lock关键字了

第一步 是lock建立互斥锁。

第二步执行lock内的方法执行完毕之后,

第三步释放lock互斥锁。

这一个过程中,只有一个线程能访问,如果有其他线程访问那就必须等待第一个线程执行完,并释放lock。

那么在代码中

 1 class CounterWidthLock : CounterBase
 2     {
 3         /// <summary>
 4         /// 判断是否锁定资源
 5         /// </summary>
 6         public readonly object _syncRoot = new object();
 7 
 8         public int Count { get; private set; }
 9 
10         /// <summary>
11         /// Lock关键字减法
12         /// </summary>
13         public override void Decrement()
14         {
15             lock (_syncRoot)//lock关键字,锁定资源
16             {
17                 Count--;
18             }
19         }
20         /// <summary>
21         /// lock关键字加法
22         /// </summary>
23         public override void Increment()
24         {
25             lock (_syncRoot)//lock关键字,锁定资源
26             {
27                 Count++;
28             }
29         }
30     }

6:锁

15,25均为lock关键字。

那么什么是锁?

可以理解为一个唯一的资源,对象。这个对象最好不是公开的。 公开的话 会造成很多不便。

也就是T1 T2 T3 虽然都同时进行了TestCounter的方法。

但因为lock的存在,一个线程在执行加减的时候,其他线程是不可以干预的。也就是T1 执行加减,也许执行了三四次,T2 T3一直在等待。三个线程都是彬彬有礼等待其他来完成他们自己的事情,之后在是自己的。虽然谦让,但也有自己的原则,就是自己在做的时候,别人是不可以干预的

 

posted @ 2018-07-04 13:51  ARM830  阅读(271)  评论(0编辑  收藏  举报