【轉】.NET中lock的使用方法及注意事項

http://daimajishu.iteye.com/blog/1079107#html

 

lock就是把一段代碼定義爲臨界區,所謂臨界區就是同一時刻只能有一個線程來操做臨界區的代碼,當一個線程位於代碼的臨界區時,另外一個線程不能進入臨界區,若是試圖進入臨界區,則只能一直等待(即被阻止),直到已經進入臨界區的線程訪問完畢,並釋放鎖旗標。安全

其基本使用方式以下:less

class Test  
{  
    //定義一個私有成員變量,用於Lock  
    private static object lockobj = new object();  
    void DoSomething()  
    {  
        lock (lockobj)  
        {  
            //須要鎖定的代碼塊  
        }  
    }  
}  

最經典的例子,莫過於模擬銀行5個窗口取錢操做的例子了,5個窗口是5個線程,都要取錢,可是同一刻只能有一個窗口能夠進行真正的取錢操做(錢數的數值計算,剩餘多少等這些代碼必須定義爲臨界區),其餘只有等待,其代碼以下:dom

 

using System;  
using System.Threading;  
  
class Account   
{  
   int balance;  
  
   Random r = new Random();  
  
   public Account(int initial)   
   {  
      balance = initial;  
   }  
  
   int Withdraw(int amount)   
   {  
  
      // This condition will never be true unless the lock statement  
      // is commented out:  
      if (balance < 0)   
      {  
         throw new Exception("Negative Balance");  
      }  
  
      // Comment out the next line to see the effect of leaving out   
      // the lock keyword:  
      lock (this)  
      {  
         if (balance >= amount)   
         {  
            Console.WriteLine("提款以前餘額(Balance before Withdrawal):  " + balance);  
            Console.WriteLine("提款數量(Amount to Withdraw)           : -" + amount);   
            balance = balance - amount;  
            Console.WriteLine("提款以後餘額(Balance after Withdrawal) :  " + balance);  
            Console.WriteLine();  
            return amount;  
         }  
         else   
         {  
            return 0; // transaction rejected  
         }  
      }  
   }  
  
   public void DoTransactions()   
   {  
      //模擬100我的來提款,每次提[1-10)元  
      for (int i = 0; i < 100; i++)   
      {  
         Withdraw(r.Next(1, 10));    
      }  
   }  
}  
class Test   
{  
   public static void Main()   
   {  
      Thread[] threads = new Thread[5];  
  
      //總額爲100元  
      Account acc = new Account (100);  
  
      //定義並初始化5個線程,模擬銀行的5個窗口  
      for (int i = 0; i < 5; i++)   
      {  
         Thread t = new Thread(new ThreadStart(acc.DoTransactions));  
         threads[i] = t;  
      }  
  
      //啓動5個線程,模擬銀行的5個窗口開始工做  
      for (int i = 0; i < 5; i++)   
      {  
          Console.WriteLine("threads[{0}].Start()", i);  
         threads[i].Start();  
      }  
   }  
} 

運算結果:測試

threads[0].Start()
threads[1].Start()
提款以前餘額(Balance before Withdrawal): 100
提款數量(Amount to Withdraw) : -4
提款以後餘額(Balance after Withdrawal) : 96
this

提款以前餘額(Balance before Withdrawal): 96
提款數量(Amount to Withdraw) : -5
提款以後餘額(Balance after Withdrawal) : 91
spa

提款以前餘額(Balance before Withdrawal): 91
提款數量(Amount to Withdraw) : -4
提款以後餘額(Balance after Withdrawal) : 87
線程

提款以前餘額(Balance before Withdrawal): 87
提款數量(Amount to Withdraw) : -9
提款以後餘額(Balance after Withdrawal) : 78
code

提款以前餘額(Balance before Withdrawal): 78
threads[2].Start()
提款數量(Amount to Withdraw) : -8
提款以後餘額(Balance after Withdrawal) : 70
htm

提款以前餘額(Balance before Withdrawal): 70
提款數量(Amount to Withdraw) : -6
提款以後餘額(Balance after Withdrawal) : 64

提款以前餘額(Balance before Withdrawal): 64
提款數量(Amount to Withdraw) : -1
提款以後餘額(Balance after Withdrawal) : 63

提款以前餘額(Balance before Withdrawal): 63
提款數量(Amount to Withdraw) : -4
提款以後餘額(Balance after Withdrawal) : 59

提款以前餘額(Balance before Withdrawal): 59
提款數量(Amount to Withdraw) : -2
提款以後餘額(Balance after Withdrawal) : 57

提款以前餘額(Balance before Withdrawal): 57
提款數量(Amount to Withdraw) : -1
提款以後餘額(Balance after Withdrawal) : 56

提款以前餘額(Balance before Withdrawal): 56
提款數量(Amount to Withdraw) : -9
提款以後餘額(Balance after Withdrawal) : 47

提款以前餘額(Balance before Withdrawal): 47
提款數量(Amount to Withdraw) : -7
提款以後餘額(Balance after Withdrawal) : 40

提款以前餘額(Balance before Withdrawal): 40
提款數量(Amount to Withdraw) : -5
提款以後餘額(Balance after Withdrawal) : 35

提款以前餘額(Balance before Withdrawal): 35
提款數量(Amount to Withdraw) : -1
提款以後餘額(Balance after Withdrawal) : 34

提款以前餘額(Balance before Withdrawal): 34
提款數量(Amount to Withdraw) : -1
提款以後餘額(Balance after Withdrawal) : 33

提款以前餘額(Balance before Withdrawal): 33
提款數量(Amount to Withdraw) : -2
提款以後餘額(Balance after Withdrawal) : 31

提款以前餘額(Balance before Withdrawal): 31
提款數量(Amount to Withdraw) : -2
提款以後餘額(Balance after Withdrawal) : 29

提款以前餘額(Balance before Withdrawal): 29
提款數量(Amount to Withdraw) : -3
提款以後餘額(Balance after Withdrawal) : 26

提款以前餘額(Balance before Withdrawal): 26
提款數量(Amount to Withdraw) : -3
提款以後餘額(Balance after Withdrawal) : 23

提款以前餘額(Balance before Withdrawal): 23
提款數量(Amount to Withdraw) : -8
提款以後餘額(Balance after Withdrawal) : 15

提款以前餘額(Balance before Withdrawal): 15
提款數量(Amount to Withdraw) : -6
提款以後餘額(Balance after Withdrawal) : 9

提款以前餘額(Balance before Withdrawal): 9
提款數量(Amount to Withdraw) : -9
提款以後餘額(Balance after Withdrawal) : 0

threads[3].Start()
threads[4].Start()
請按任意鍵繼續. . .

發現窗口1 threads[1].Start()和窗口2 threads[2].Start()先進行取錢,等窗口3 threads[3].Start()和窗口4 threads[4].Start()去取錢的時候,已經沒錢了。

使用lock須要注意的地方:

1.lock不能鎖定空值
某一對象能夠指向Null,但Null是不須要被釋放的。(請參考:認識全面的null
2.lock不能鎖定string類型,雖然它也是引用類型的。由於字符串類型被CLR「暫留」
這意味着整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了全部運行的應用程序域的全部線程中的該文本。所以,只要在應用程序進程中的任何位置處具備相同內容的字符串上放置了鎖,就將鎖定應用程序中該字符串的全部實例。所以,最好鎖定不會被暫留的私有或受保護成員。
3.lock鎖定的對象是一個程序塊的內存邊界
4.值類型不能被lock,由於前文標紅字的「對象被釋放」,值類型不是引用類型的

5.lock就避免鎖定public 類型或不受程序控制的對象。
例如,若是該實例能夠被公開訪問,則 lock(this) 可能會有問題,由於不受控制的代碼也可能會鎖定該對象。這可能致使死鎖,即兩個或更多個線程等待釋放同一對象。出於一樣的緣由,鎖定公共數據類型(相比於對象)也可能致使問題。
使用lock(this)的時候,類的成員變量的值可能會被不在臨界區的方法改值了

以下面的測試:

using System.Threading;  
using System;  
public class ThreadTest  
{  
    private int i = 0;  
    public void Test()  
    {  
        Thread t1 = new Thread(Thread1);  
        Thread t2 = new Thread(Thread2);  
        t1.Start();  
        t2.Start();  
    }  
    public void Thread1()  
    {  
        lock (this)  
        {  
            Console.WriteLine(this.i);  
            Thread.Sleep(1000);  
            Console.WriteLine(this.i);  
        }  
    }  
    public void Thread2()  
    {  
        Thread.Sleep(500);  
        this.i = 1;  
        Console.WriteLine("Change the value in locking");  
    }  
}  
public class ThreadTest2  
{  
    private int i = 0;  
    public void Test()  
    {  
        Thread t1 = new Thread(Thread1);  
        Thread t2 = new Thread(Thread2);  
        t1.Start();  
        t2.Start();  
    }  
    public void Thread1()  
    {  
        lock (this)  
        {  
            Console.WriteLine(this.i);  
            Thread.Sleep(1000);  
            Console.WriteLine(this.i);  
        }  
    }  
    public void Thread2()  
    {  
        lock (this)  
        {  
            Thread.Sleep(500);  
            this.i = 1;  
            Console.WriteLine("Can't change the value in locking");  
        }  
    }  
}  
public class ThreadMain  
{  
    public static void Main()  
    {  
        //ThreadTest b = new ThreadTest();  
        //Thread t = new Thread(new ThreadStart(b.Test));  
        //t.Start();  
  
        ThreadTest2 b2 = new ThreadTest2();  
        Thread t2 = new Thread(new ThreadStart(b2.Test));  
        t2.Start();  
    }  
}  

測試ThreadTest的運行結果:

0
Change the value in locking
1
請按任意鍵繼續. . .

測試ThreadTest2的運行結果:

0
0
Can't change the value in locking
請按任意鍵繼續. . .

發現第一個測試裏成員變量i被改值了。

本想在案例一中lock住this對象,讓其餘的線程不能操做,但是事情不是像咱們想象的那樣lock(this)是lock this的意思.this中的屬性依然可以被別的線程改變.那咱們lock住的是什麼?是代碼段,是lock後面大括號中代碼段,這段代碼讓多我的執行不不被容許的.那返回頭來在看lock(this),this是什麼意思呢?能夠說this知識這段代碼域的標誌,看看案例二中Thread2.Thread2就明白了,Thread2中的lock須要等到Thread1種lock釋放後纔開始運行,釋放以前一直處於等待狀態,這就是標誌的表現.
好吧,讓咱們來了解一下,lock這段代碼是怎麼運行的.lock語句根本使用的就是Monitor.Enter和Monitor.Exit,也就是說lock(this)時執行Monitor.Enter(this),大括號結束時執行Monitor.Exit(this).他的意義在於什麼呢,對於任何一個對象來講,他在內存中的第一部分放置的是全部方法的地址,第二部分放着一個索引,他指向CLR中的SyncBlock Cache區域中的一個SyncBlock.什麼意思呢?就是說,當你執行Monitor.Enter(Object)時,若是object的索引值爲負數,就從SyncBlock Cache中選區一個SyncBlock,將其地址放在object的索引中。這樣就完成了以object爲標誌的鎖定,其餘的線程想再次進行Monitor.Enter(object)操做,將得到object爲正數的索引,而後就等待。直到索引變爲負數,即線程使用Monitor.Exit(object)將索引變爲負數。
若是明白了Monitor.Enter的原理,lock固然再也不話下.固然lock後括號裏面的值不是說把整個對象鎖住,而是對他的一個值進行了修改,使別的lock不能鎖住他,這纔是lock(object)的真面目.

但在實際使用中Monitor仍是不推薦,仍是lock好的,Monitor須要加上不少try catch才能保證安全性,但lock卻幫咱們作了,並且lock看起來更優雅.在靜態方法中如何使用lock呢,因爲咱們沒有this可用,因此咱們使用typeof(this)好了,Type也有相應的方法地址和索引,因此他也是能夠來看成lock的標誌的.但微軟不提倡是用public的object或者typeof()或者字符串這樣的標誌就是由於,若是你的public object在其餘的線程中被null並被垃圾收集了,將發生不可預期的錯誤.

相關文章
相關標籤/搜索