由單例模式學到:lock鎖

lock 關鍵字將語句塊標記爲臨界區,方法是獲取給定對象的互斥鎖,執行語句,而後釋放該鎖。多線程

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

lock 關鍵字可確保當一個線程位於代碼的臨界區時,另外一個線程不會進入該臨界區。 若是其餘線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。ide

用實例說話:post

例1字體

新建多個線程,用多個線程的操做來模擬實現lock的場景ui

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的實例uthis

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

以上代碼段執行User類的addtion方法,並獲得返回值。即:此時有50個線程在執行同一個User實例的u的addtion方法,在執行addtion方法的時候,addtion方法中的lock(this)起到鎖的做用,當第一個線程進入lock代碼塊中後,將會把其餘的線程阻塞到外面,直到第一個線程執行完,鎖獲得釋放,其餘線程中的一個才得以執行。線程

public class User
{調試

  private static int sin;code

  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)的狀態是未上鎖。實際上兩個方法是同時執行的,只不過是由於打上了斷電,才形成只能看到一步一步的操做!
複製代碼
相關文章
相關標籤/搜索