• 由单例模式学到:lock锁


    lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。

    lock (xxx)
    {
        // Critical code section.
    }

    lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

    用实例说话:

    例1

    新建多个线程,用多个线程的操作来模拟实现lock的场景

    public static void fun()
    {
      Thread[] threads = new Thread[500];
      User u = new User();
      for (int i = 0; i < 50; i++)
      {
        threads[i] = new Thread(new ParameterizedThreadStart(getInstance));
        threads[i].IsBackground = true;
        threads[i].Start(u);
      }
    }
    以上新建了50个线程,并且让每个线程都执行getInstance方法,并且每个getInstance方法的参数为User的实例u

    public static void getInstance(object obj)
    {
      User u = (User)obj;
      Console.WriteLine(u.addtion());
    }

    以上代码段执行User类的addtion方法,并得到返回值。即:此时有50个线程在执行同一个User实例的u的addtion方法,在执行addtion方法的时候,addtion方法中的lock(this)起到锁的作用,当第一个线程进入lock代码块中后,将会把其他的线程阻塞到外面,直到第一个线程执行完,锁得到释放,其他线程中的一个才得以执行。

    public class User
    {

      private static int sin;

      public string addtion()
      {
        lock (this)
        {
          sin = sin + 10;
          Thread.Sleep(50);
          return sin.ToString();
        }
      }
    }
    如上的代码,如果lock(this)起到锁的作用的话,线程一个一个执行,每个线程的执行sin的值都会增加10。期望的效果为:10,20,30,40,50....
    =====执行的效果为

      注意上述代码中的红色字体标记的部分,多个线程执行的都是同一个User实例的addtion方法,所以lock(this)起到了锁了作用,如果让每个线程都实例化一个User类,再去执行自己实例化的User类的addtion方法,那么lock(this)就起不到作用了!
    即:

    public static void fun()
    {
      Thread[] threads = new Thread[500];
      for (int i = 0; i < 50; i++)
      {
        threads[i] = new Thread(new ParameterizedThreadStart(getInstance));
        threads[i].IsBackground = true;
        threads[i].Start(i);
      }
    }
    public static void getInstance(object obj)
    {
      User u = new User();
      Console.WriteLine(u.addtion());
    }

      如果是上述代码的话,则lock就锁不住,lock块中可能有多个线程在执行,即:如果第一个线程把sin设置为10,再第一个线程还没有结束之前,第二个线程会再将sin加10,如此循环,n个线程执行之后,那么第一个线程执行结束后,sin的值就变成了n*10。
    执行效果为:

      实例中还涉及到静态字段,静态字段在IL中标记为BeforeFieldInit,即:静态字段是由程序自动执行,与普通字段不同的是,它仅执行一次,在之后类的实例化时,也不需要再此执行。从而上述代码中sin的字段才得以保存。如果sin字段的static去掉的话,则输出的就是:10,10,10,10....

      由此即可得到结论:lock(this) 锁定 当前实例对象,如果有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。

    lock中的this表示的是当前实例,对于同一个实例的多线程来说,当第一个现成进来的时,通过lock(this)将当前实例上锁,那么当此实例的其他线程进来的时候,该实例已经上锁,所以就不能进入;而对于每个线程都实例化一个对象就不一样了,当第一个线程进来的时候,lock将第一个线程的实例上锁,那么之后所有的第一个线程实例化的对象就不能再进入访问,这里仅仅是对第一个线程实例化的对象,如果是除此之外的所有其他的对象的话,是起不到锁的作用,即:当第二个线程、第三个线程、、等,到达lock时,是不被锁上的!

    例2

    既然lock(this)只能防止当前实例多线程访问,不能防止其他实例进入!对于上述的内容如果了解之后,不难想到如何做到绝对防止其他线程(无论是什么实例)进入lock的逻辑块,其实就来实例化一个任意的对象(静态的),例如:private static object obj=new object();lock(obj);因为静态的对象只是由程序自动执行一次,之后再实例化时,用得还是原来的,下面就来说一下访问流程,新建n个任意实例的线程,当第一个线程进来时,利用lock将obj上锁,第一个线程进入lock逻辑块中执行,当第二个、第三个等线程进来时,因为obj是静态的对象,所有obj的值还是第一线程给设置的状态,即:是上锁的。所以其他线程是无法进入的。

        public class User
        {
            private static object obj = new object();
            private static int sin = 0;
            public string addtion()
            {
                lock (obj)
                {
                    sin = sin + 10;
                    Thread.Sleep(500);
                    return sin.ToString();
                }
            }
        }

    注意:1.必须是静态字段。不然的话,每次对象的实例化时都会执行一次object obj = new object() ,那么obj每次都是新实例化的对象,其状态肯定是没有上锁的,那样的话就无法起到对任意类型进行锁的操作;2.这里用了objct对象,其实其他对象也是可以的,例如:privatestatic List<User> obj = new List<User>(); 也是可以的,lock的参数其实就是一个变量,当有线程进入的时候,将变量的状态设置为锁,当其他线程到达时,读取到变量的状态如果是锁的,那么就等待,如果不是锁的,那么就进入lock代码块去执行;3.lock的参数其实就是充当变量,当第一个线程到达时候,将其设置为锁状态,当此线程执行完lock代码块中的逻辑后,再将此变量设置成未锁状态!至于上述例子中讨论的不同情况,就是变量如何取值的问题了。

    MSDN上说:

    通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)lock (typeof (MyType)) 和 lock ("myLock")违反此准则:

    • 如果实例可以被公共访问,将出现 lock (this) 问题。

    • 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。

    • 由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock("myLock") 问题。

    最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。

    ===分析上述的说法:lock(this)出现的问题指的是:lock(this)只对本实例进行锁,其他实例的话就起不到锁的效果,因为lock(this)是将本实例的状态设置为锁,其他实例到达时,this又是值得自己的实例,而不是上次设置为锁的实例,所有就起不到锁的效果了;lock(typeof(MyType))出现的问题是指:他可以对MyType类型的所有实例进行锁的效,果。因为lock(MyType)是将此对象的状态设置为锁,而所有MyType类的实例化都是一个对象MyType,所以他就可以起到对所有实例的锁的效果。虽然其从效果上达到了要求,但是微软现在建议不要使用 lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问 该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起(红色字体表示看不懂);lock("myLock")出现的问题指的是:因为string是引用类型,如果多个变量的值为“myLock”,那么如果lock("myLock")之后,就是将“myLock”的状态设置为锁,那么在栈中的变量名都指向在堆中的“myLock”,即:当前都是锁的状态了,如果其中一个变量名为str1,那么存在lock("myLock")的同时也存在lock(str1),将两个lock设置在两个方法中,并且新建n个线程来执行此两个方法,就会出现当有一个线程执行lock("myLock")之后,lock(str1)的代码块也上锁了。

      例:lock("myLock")

    复制代码
            static void Main(string[] args)
            {
                Thread[] threads = new Thread[500];
                for (int i = 0; i < 50; i=i+2)
                {
                    threads[i] = new Thread(new ParameterizedThreadStart(fun1));
                    threads[i+1] = new Thread(new ParameterizedThreadStart(fun2));
                    threads[i].IsBackground = true;
                    threads[i+1].IsBackground = true;
                    threads[i].Start(null);
                    threads[i+1].Start(null);
                }
                Console.ReadKey();
            }
            public static void fun1(object obj)
            {
                User u = new User();
                Console.WriteLine("fun1:  "+u.addtion1());
            }
            public static void fun2(object obj)
            {
                User u = new User();
                Console.WriteLine("fun2:"+u.addtion2());
            }
    复制代码
    复制代码
    public class User
        {
            private static object obj = new object();
            private static string str1 = "123";
            private static int sin1 = 0;
            private static int sin2 = 1000;
            public string addtion1()
            {
                lock ("123")
                {
                    sin1 = sin1 + 10;
                    Thread.Sleep(100);
                    return sin1.ToString() + "-" + DateTime.Now.Ticks.ToString();
                }
            }
            public string addtion2()
            {
                lock (str1)
                {
                    sin2 = sin2 + 10;
                    Thread.Sleep(100);
                    return sin2.ToString() + "-" + DateTime.Now.Ticks.ToString();
                }
            }
        }
    打上断点,调试看就可看出。当线程在lock ("123")中执行时,即使到了Thread.Sleep(100);也会等待,然后再继续执行,这就说明此时lock (str1)也是锁的状态!这就是问题所在!
    
        public class User
        {
            private static object obj = new object();
            private static string str1 = "12345";
            private static int sin1 = 0;
            private static int sin2 = 1000;
            public string addtion1()
            {
                lock ("123")
                {
                    sin1 = sin1 + 10;
                    Thread.Sleep(100);
                    return sin1.ToString() + "-" + DateTime.Now.Ticks.ToString();
                }
            }
            public string addtion2()
            {
                lock (str1)
                {
                    sin2 = sin2 + 10;
                    Thread.Sleep(100);
                    return sin2.ToString() + "-" + DateTime.Now.Ticks.ToString();
                }
            }
        }
    打上断点调试看,当线程在lock ("123")中执行时,当到达Thread.Sleep(100)时,lock (str1)中的代码就会执行,这说明此时lock (str1)的状态是未上锁。实际上两个方法是同时执行的,只不过是因为打上了断电,才造成只能看到一步一步的操作!
    复制代码

     

     

  • 相关阅读:
    公司系统缓慢-分析数据库CPU过高原因-连接数
    克隆虚拟机的相关设置
    centos7基本设置和优化
    遇到的问题和解决集中
    chrome优化调教
    shell三剑客之小弟grep
    迭代器和生成器
    函数进阶
    函数介绍&参数的使用
    文件处理中光标的移动以及文件的修改
  • 原文地址:https://www.cnblogs.com/wupeiqi/p/3275867.html
Copyright © 2020-2023  润新知