由单例模式学到: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)的状态是未上锁。实际上两个方法是同时执行的,只不过是因为打上了断电,才造成只能看到一步一步的操作!
复制代码

 

 

posted @ 2013-08-22 20:14  武沛齐  阅读(911)  评论(0编辑  收藏  举报