• c#线程安全


    先看一个例子:

       /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
                for (int i = 0; i < 5; i++)
                {
                    Task.Run(() =>
                    { 
                        Console.WriteLine($"This is {i}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                        Thread.Sleep(2000); 
                        Console.WriteLine($"This is {i}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
    
                    });
                }
    
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

     结果:

    *** **   btnSafe_Click start -主线程ID:1-2021-09-18 08:44:36***** ***
    This is 1  start -3 
    This is 3  start -4 
    This is 4  start -7 
    This is 5  start -6 
    This is 5  start -9 
    *** ****  btnSafe_Click end  -主线程ID:1-2021-09-18 08:44:36********** *
    This is 5  end -7 
    This is 5  end -6 
    This is 5  end -9 
    This is 5  end -4 
    This is 5  end -3 

    从结果我们可以看出来多个线程中的i值在实际执行中是相同的,都是5,按照正常逻辑应该是0 1 2 3 4这4个值,为什么会出现这种情况呢?首先,必须要知道线程是属于操作系统的,操作系统根据调度策略来分配线程,不是实时响应的,可能等到i已经循环到5了才开始执行这个线程。

    我们可以做一下更改,设置一个局部变量,这样每循环一次都会产生一个新的局部变量值,互不干扰。

            /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        Console.WriteLine($"This is {i} -- {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                        Thread.Sleep(2000);
                        Console.WriteLine($"This is {i} -- {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
    
                    });
                }
    
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

    结果:

    *** **   btnSafe_Click start -主线程ID:1-2021-09-18 09:05:25***** ***
    This is 0 -- 0  start -3 
    This is 5 -- 1  start -5 
    *** ****  btnSafe_Click end  -主线程ID:1-2021-09-18 09:05:25********** *
    This is 5 -- 4  start -8 
    This is 5 -- 3  start -7 
    This is 5 -- 2  start -6 
    This is 5 -- 4  end -8 
    This is 5 -- 0  end -3 
    This is 5 -- 2  end -6 
    This is 5 -- 3  end -7 
    This is 5 -- 1  end -5 

    案例2:

    多线程同时访问一个集合一般是没什么问题的,但是线程安全问题一般是出现在修改一个对象的时候。

    多线程安全定义:一段代码,单线程执行和多线程执行的结果不一致,就表明有线程安全问题。

            /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***"); 
                #region MyRegion
                List<int> intList = new List<int>();
                for (int i = 0; i < 10000; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        intList.Add(i); 
                    });
                }
    
                Thread.Sleep(5000);
                Console.WriteLine(intList.Count);
                #endregion
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

    结果:

    *** **   btnSafe_Click start -主线程ID:1-2021-09-18 09:32:12***** ***
    9997
    *** ****  btnSafe_Click end  -主线程ID:1-2021-09-18 09:32:17********** *

    结果的长度是9997,小于10000,其实下一次执行的时候结果可能就变成另一个了,这都是不确定的,上面的代码是想让每一个线程都单独添加一个数,比如说线程1添加数0,线程2添加数1,,,,,知道10000,。但是线程的请求不是实时的,导致最终会导致intList中存在大量的重复数据。数组在内存中是连续存在的, 如果在同一时刻上同时由多个线程同时在这个内存位置上做更改,就会出现覆盖,所以存在数据丢失。

    解决数据丢失问题:

        private static readonly object LOCK = new object();
     
      /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***"); 
                #region MyRegion
                List<int> intList = new List<int>();
                for (int i = 0; i < 10000; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    { 
                        lock(LOCK)
                        {
                            intList.Add(i); 
                        }
                         
                    });
                }
    
                Thread.Sleep(5000);
                Console.WriteLine(intList.Count);
                #endregion
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

    加了lock就保证每次lock方法体中每一次都只能有一个线程进入,此时intList数量为10000。但是还是解决不了数据重复的问题。要解决数据重复的话就做一个局部变量,和第一个案例一样就可以了。

    Lock原理:Lock其实是一个语法糖,等价于Monitor,是锁定一个内存引用地址,对这个引用对象做了一个状态标识,但是不能锁定值类型以及null。等价于下面的写法:

    try
    {    
        Monitor.Enter(obj) 
    }
    catch()
    {}
    finally
    {
          Monitor.Exit(obj) 
    }

    案例3:Lock相关问题

    如果使用共同的一个变量来进行锁定:

            /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
                TestLock.Show();
                #region MyRegion
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        lock (TestLock.LOCK)
                        {
                            Console.WriteLine($" MainShow This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            Thread.Sleep(2000);
                            Console.WriteLine($" MainShow This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                        }
                    });
                }
                #endregion 
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }
        /// <summary>
        /// 测试Lock类
        /// </summary>
        public class TestLock
        {
            public static readonly object LOCK = new object();
            public static void Show()
            {
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        lock (LOCK)
                        {
                            Console.WriteLine($" TestLockShow This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            Thread.Sleep(2000);
                            Console.WriteLine($" TestLockShow This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
    
                        }
                    });
                }
            }
    
        }

    结果:

    *** **   btnSafe_Click start -主线程ID:1-2021-09-18 10:43:18***** ***
     TestLockShow This is  0  start -13 
    *** ****  btnSafe_Click end  -主线程ID:1-2021-09-18 10:43:18********** *
     TestLockShow This is  0  end -13 
     TestLockShow This is  1  start -22 
     TestLockShow This is  1  end -22 
     MainShow This is  4  start -13 
     MainShow This is  4  end -13 
     TestLockShow This is  3  start -24 
     TestLockShow This is  3  end -24 
     TestLockShow This is  2  start -23 
     TestLockShow This is  2  end -23 
     TestLockShow This is  4  start -25 
     TestLockShow This is  4  end -25 
     MainShow This is  0  start -26 
     MainShow This is  0  end -26 
     MainShow This is  1  start -27 
     MainShow This is  1  end -27 
     MainShow This is  2  start -28 
     MainShow This is  2  end -28 
     MainShow This is  3  start -29 
     MainShow This is  3  end -29 

    通过结果可以看出来这是同步执行的因为不管是执行TestLockShow还是MainShow,必须是同一个线程进入这个区域之后开始结束是连续的,2个方法没有混起来,所以共用一个相同的锁变量就会出现相互阻塞。

    如果更改代码,锁变量不共用:

         /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
                TestLock.Show();
                #region MyRegion
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        lock ( LOCK)
                        {
                            Console.WriteLine($" MainShow This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            Thread.Sleep(2000);
                            Console.WriteLine($" MainShow This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                        }
                    });
                }
                #endregion 
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

    结果:

     可以看出来此时是并发执行的。所以最好不要共用。

    案例3:锁变量如果不是静态变量会如何?

    代码:

      /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
                TestLock.Show();
                {
                    TestLock testLock = new TestLock();
                    testLock.ShowTemp(1);
    
                    TestLock testLock1 = new TestLock();
                    testLock1.ShowTemp(2);
                }
                  
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

    测试类:

     /// <summary>
        /// 测试Lock类
        /// </summary>
        public class TestLock
        {
            private static readonly object LOCK = new object();
            public static void Show()
            {
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        lock (LOCK)
                        {
                            Console.WriteLine($" TestLockShow This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            Thread.Sleep(2000);
                            Console.WriteLine($" TestLockShow This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
    
                        }
                    });
                }
            }
    
            private   readonly object LOCKTemp = new object();
            public void ShowTemp(int index)
            {
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        lock (LOCKTemp)
                        {
                            Console.WriteLine($"index={index}- ShowTemp This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            Thread.Sleep(2000);
                            Console.WriteLine($" index={index}- ShowTemp This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
    
                        }
                    });
                }
            }
    
        }

    结果:

    *** **   btnSafe_Click start -主线程ID:1-2021-09-18 12:00:47***** ***
     
    *** ****  btnSafe_Click end  -主线程ID:1-2021-09-18 12:00:47********** *
     TestLockShow This is  0  start -7 
    index=1- ShowTemp This is  1  start -4 
     TestLockShow This is  0  end -7 
     TestLockShow This is  3  start -3 
    index=2- ShowTemp This is  0  start -7 
     index=1- ShowTemp This is  1  end -4 
    index=1- ShowTemp This is  0  start -6 
     TestLockShow This is  3  end -3 
     TestLockShow This is  4  start -5 
     index=2- ShowTemp This is  0  end -7 
    index=2- ShowTemp This is  1  start -4 
     index=1- ShowTemp This is  0  end -6 
    index=1- ShowTemp This is  2  start -10 
     TestLockShow This is  4  end -5 
     TestLockShow This is  1  start -8 
     index=2- ShowTemp This is  1  end -4 
    index=2- ShowTemp This is  4  start -3 
     index=1- ShowTemp This is  2  end -10 
    index=1- ShowTemp This is  3  start -11 
     TestLockShow This is  1  end -8 
     TestLockShow This is  2  start -9 
     index=2- ShowTemp This is  4  end -3 
    index=2- ShowTemp This is  2  start -13 
     index=1- ShowTemp This is  3  end -11 
    index=1- ShowTemp This is  4  start -12 
     TestLockShow This is  2  end -9 
     index=2- ShowTemp This is  2  end -13 
    index=2- ShowTemp This is  3  start -14 
     index=1- ShowTemp This is  4  end -12 
     index=2- ShowTemp This is  3  end -14 

    可以看出来是并发执行的,原因就在于非静态成员变量在new类对象的时候都会重新初始化一次,这2次调用,锁变量就不是同一个值了,在堆中存的地址也不一样了。所以最好设置为静态变量。

    案例3:锁变量为string类型

    string的不变性和驻留性

    代码:

           private readonly string LockString = "哈哈";
            /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
                TestLock.Show();
                {
                    TestLock testLock = new TestLock();
                    testLock.ShowString(1);
                    for (int i = 0; i < 5; i++)
                    {
                        int k = i;
                        Task.Run(() =>
                        {
                            lock (LockString)
                            {
                                Console.WriteLine($" MainShow This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                                Thread.Sleep(2000);
                                Console.WriteLine($" MainShow This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            }
                        });
                    }
                }
     
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

    测试类:

      /// <summary>
        /// 测试Lock类
        /// </summary>
        public class TestLock
        {  private readonly string LockString  = "哈哈";
            public void ShowString(int index)
            {
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        lock (LockString)
                        {
                            Console.WriteLine($"index={index}- ShowString This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            Thread.Sleep(2000);
                            Console.WriteLine($" index={index}- ShowString This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
    
                        }
                    });
                }
            }
    
        }

      结果:

     **   btnSafe_Click start -主线程ID:1-2021-09-18 15:22:07***** *** *** ****  btnSafe_Click end  -主线程ID:1-2021-09-18 15:22:07********** *
     ShowString This is  0  start -3 
      ShowString This is  0  end -3 
     MainShow This is  0  start -4 
     MainShow This is  0  end -4 
     ShowString This is  2  start -5 
      ShowString This is  2  end -5 
     MainShow This is  2  start -6 
     MainShow This is  2  end -6 
     ShowString This is  1  start -7 
      ShowString This is  1  end -7 
     ShowString This is  4  start -8 
      ShowString This is  4  end -8 
     ShowString This is  3  start -9 
      ShowString This is  3  end -9 
     MainShow This is  1  start -10 
     MainShow This is  1  end -10 
     MainShow This is  3  start -11 
     MainShow This is  3  end -11 
     MainShow This is  4  start -12 
     MainShow This is  4  end -12 

    因为锁定的是内存引用,字符串是享元的,这2个字符串都是相同的值,虽然在栈上的地址不一样但是在堆中的地址是一样的,所以相当于锁的都是同一个,所以是不能并发的。

    CLR中维护着一个驻留池(Intern Pool)的散列表(HashTable),这个表记录了所有在代码中使用字面量声明的字符串实例的引用 ,使用字面量声明的字符串都会被记录到散驻留池(散列表 键为字符串 值为字符串存储地址),

    如:string str="abc"; 或 string str="a"+"bc"这种就可以称为字面量

    但是 string str=变量+变量 或者 变量+字符串 这种都不能称为字面量

    比如前后2个字符串的内容一致,那么在堆中的地址是一样的,这样就是同一个引用了。最好不要用。

        /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
    
                {
    TestLockGeneric
    <int>.Show(1); TestLockGeneric<int>.Show(2); TestLockGeneric<TestLock>.Show(3); } Console.WriteLine($"*** **** btnSafe_Click end -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *"); }

    测试类:

        /// <summary>
        /// 测试Lock类
        /// </summary>
        public class TestLockGeneric<T>
        {
            private static readonly object LOCK = new object();
            public static void Show(int index)
            {
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        lock (LOCK)
                        {
                            Console.WriteLine($" TestLockGeneric This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            Thread.Sleep(2000);
                            Console.WriteLine($" TestLockGeneric This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
    
                        }
                    });
                }
            } 
    
        }

    结果:

    *** **   btnSafe_Click start -主线程ID:1-2021-09-18 16:00:32***** ***  TestLockGeneric This is  0  start -3 
    *** ****  btnSafe_Click end  -主线程ID:1-2021-09-18 16:00:33********** *
     TestLockGeneric This is  0  end -3 
     TestLockGeneric This is  4  start -4 
     TestLockGeneric This is  0  start -3 
     TestLockGeneric This is  4  end -4 
     TestLockGeneric This is  1  start -5 
     TestLockGeneric This is  0  end -3 
     TestLockGeneric This is  1  start -13 
     TestLockGeneric This is  1  end -5 
     TestLockGeneric This is  2  start -6 
     TestLockGeneric This is  1  end -13 
     TestLockGeneric This is  4  start -3 
     TestLockGeneric This is  2  end -6 
     TestLockGeneric This is  0  start -7 
     TestLockGeneric This is  4  end -3 
     TestLockGeneric This is  3  start -4 
     TestLockGeneric This is  0  end -7 
     TestLockGeneric This is  3  start -8 
     TestLockGeneric This is  3  end -4 
     TestLockGeneric This is  2  start -14 
     TestLockGeneric This is  3  end -8 
     TestLockGeneric This is  2  start -9 
     TestLockGeneric This is  2  end -14 
     TestLockGeneric This is  2  end -9 
     TestLockGeneric This is  1  start -10 
     TestLockGeneric This is  1  end -10 
     TestLockGeneric This is  3  start -11 
     TestLockGeneric This is  3  end -11 
     TestLockGeneric This is  4  start -12 
     TestLockGeneric This is  4  end -12 

    因为泛型类在类型参数相同时候是同一类,参数类型不同时候就不是同一类,所以1与2是不并发,1与3是并发的。

    案例4:Lock(this)

    代码:

            /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
    
                {
                    TestLock testLock = new TestLock();
                    testLock.ShowThis(1);
    
                    TestLock testLock2 = new TestLock();
                    testLock2.ShowThis(2);
                }
     
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

    测试类:

        /// <summary>
        /// 测试Lock类
        /// </summary>
        public class TestLock
        {
            public void ShowThis(int index)
            {
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Task.Run(() =>
                    {
                        //this是当前实例
                        lock (this)
                        {
                            Console.WriteLine($" ShowThis This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            Thread.Sleep(2000);
                            Console.WriteLine($"  ShowThis This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
    
                        }
                    });
                }
            } 
    
        }

    这个不用看就知道肯定是会出现并发,因为this是当前实例,没法判断。

    还有一种写法:

            /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
    
                {
                  TestLock testLock = new TestLock();
                    testLock.ShowThis(1);
                    for (int i = 0; i < 5; i++)
                    {
                        int k = i;
                        Task.Run(() =>
                        {
                            lock (testLock)
                            {
                                Console.WriteLine($" MainShow This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                                Thread.Sleep(2000);
                                Console.WriteLine($" MainShow This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                            }
                        });
                    }
                }
     
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }

    这种肯定是不会并发的,因为ShowThis中的用的是lock(this),和 lock (testLock)中的testLock是同一个实例,而且都是引用类型。

    案例5:Lock(this) 的死锁问题

    代码:

      /// <summary>
            /// 多线程安全
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnSafe_Click(object sender, EventArgs e)
            {
                Console.WriteLine($"*** **   btnSafe_Click start -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}***** ***");
    
                {
                    TestLock testLock = new TestLock();
                    testLock.ShowThisAnother(1);
                     
                }
     
                Console.WriteLine($"*** ****  btnSafe_Click end  -主线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}-{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}********** *");
            }
        }

    测试类:

        /// <summary>
        /// 测试Lock类
        /// </summary>
        public class TestLock
        {
            private int _Num = 0;
            public void ShowThisAnother(int index)
            {
                this._Num++;
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    //this是当前实例
                    lock (this)
                    {
                        Console.WriteLine($" ShowThis This is  {k}  start -{Thread.CurrentThread.ManagedThreadId.ToString()} ");
                        Thread.Sleep(1000);
                        Console.WriteLine($"  ShowThis This is  {k}  end -{Thread.CurrentThread.ManagedThreadId.ToString()} "); 
                        if (this._Num < 5)
                        {
                            this.ShowThisAnother(index);
                        }
                        else
                        {
                            break;
                        }
                    }
    
                }
            } 
    
        }

    结果:不会死锁。

    死锁概念

    只所以不会死锁是因为lock(this)中的this是当前实例,  if (this._Num < 5)的时候再次调用ShowThisAnother这个方法,此时的this和上一次的this就不同了,所以不会形成死锁。因为此时锁不住,第二次调这个方法的时候是可以直接进行lock方法体中的,如下图,这里的this中包含了3个参数,其中没调一次这个方法,_Num都会递增,所以每次的this都不同,但是如果去掉这个_Num成员变量,就会发生死锁,因为不管调用多少次,LOCKTemp和LockString的值都不变,都是一样的值,所以形成了死锁。

  • 相关阅读:
    greenDao 学习之坑 "java-gen" 目录下的类不能引用
    fastboot 刷system.img 提示 sending 'system' (*KB)... FAILED (remote: data too large)
    AndroidStudio导入第三方开源库 --文件夹源码
    git 克隆项目 与 分支简单操作
    Jquery当选中后费用或什么信息会自增长
    Jquery中各种标签的含义集合
    Jquery判断是否重复(连接到后台数据库进行比较)
    Jquery按空格键选中复选框或单选框
    JS(JQuery)操作Array的相关方法
    前端知识点-人资相关知识点
  • 原文地址:https://www.cnblogs.com/anjingdian/p/15306084.html
Copyright © 2020-2023  润新知