1. 程式人生 > >[java]lock鎖介紹

[java]lock鎖介紹

  以下是本文目錄大綱:

  一.synchronized的缺陷

  二.java.util.concurrent.locks包下常用的類

  三.鎖的相關概念介紹

  若有不正之處請多多諒解,並歡迎批評指正。

  請尊重作者勞動成果,轉載請標明原文連結:

   http://www.cnblogs.com/dolphin0520/p/3923167.html

一.synchronized的缺陷

  synchronized是java中的一個關鍵字,也就是說是Java語言內建的特性。那麼為什麼會出現Lock呢?

  在上面一篇文章中,我們瞭解到如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況:

  1)獲取鎖的執行緒執行完了該程式碼塊,然後執行緒釋放對鎖的佔有;

  2)執行緒執行發生異常,此時JVM會讓執行緒自動釋放鎖。

  那麼如果這個獲取鎖的執行緒由於要等待IO或者其他原因(比如呼叫sleep方法)被阻塞了,但是又沒有釋放鎖,其他執行緒便只能乾巴巴地等待,試想一下,這多麼影響程式執行效率。

  因此就需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。

  再舉個例子:當有多個執行緒讀寫檔案時,讀操作和寫操作會發生衝突現象,寫操作和寫操作會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。

  但是採用synchronized關鍵字來實現同步的話,就會導致一個問題:

  如果多個執行緒都只是進行讀操作,所以當一個執行緒在進行讀操作時,其他執行緒只能等待無法進行讀操作。

  因此就需要一種機制來使得多個執行緒都只是進行讀操作時,執行緒之間不會發生衝突,通過Lock就可以辦到。

  另外,通過Lock可以知道執行緒有沒有成功獲取到鎖。這個是synchronized無法辦到的。

  總結一下,也就是說Lock提供了比synchronized更多的功能。但是要注意以下幾點:

  1)Lock不是Java語言內建的,synchronized是Java語言的關鍵字,因此是內建特性。Lock是一個類,通過這個類可以實現同步訪問;

  2)Lock和synchronized有一點非常大的不同,採用synchronized不需要使用者去手動釋放鎖,當synchronized方法或者synchronized程式碼塊執行完之後,系統會自動讓執行緒釋放對鎖的佔用;而Lock則必須要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。

二.java.util.concurrent.locks包下常用的類

  下面我們就來探討一下java.util.concurrent.locks包中常用的類和介面。

  1.Lock

  首先要說明的就是Lock,通過檢視Lock的原始碼可知,Lock是一個介面:

1 2 3 4 5 6 7 8 public  interface  Lock {      void  lock();      void  lockInterruptibly()  throws  InterruptedException;      boolean  tryLock();      boolean  tryLock( long  time, TimeUnit unit)  throws  InterruptedException;      void  unlock();      Condition newCondition(); }

   下面來逐個講述Lock介面中每個方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來獲取鎖的。unLock()方法是用來釋放鎖的。newCondition()這個方法暫且不在此講述,會在後面的執行緒協作一文中講述。

  在Lock中聲明瞭四個方法來獲取鎖,那麼這四個方法有何區別呢?

  首先lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他執行緒獲取,則進行等待。

  由於在前面講到如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用Lock來進行同步的話,是以下面這種形式去使用的:

1 2 3 4 5 6 7 8 9 Lock lock = ...; lock.lock(); try {      //處理任務 } catch (Exception ex){       } finally {      lock.unlock();    //釋放鎖 }

  tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他執行緒獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。

  tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

  所以,一般情況下通過tryLock來獲取鎖時是這樣使用的:

1 2 3 4 5 6 7 8 9 10 11 12 Lock lock = ...; if (lock.tryLock()) {       try {           //處理任務       } catch (Exception ex){                 } finally {           lock.unlock();    //釋放鎖       } else  {      //如果不能獲取鎖,則直接做其他事情 }

   lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果執行緒正在等待獲取鎖,則這個執行緒能夠響應中斷,即中斷執行緒的等待狀態。也就使說,當兩個執行緒同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時執行緒A獲取到了鎖,而執行緒B只有在等待,那麼對執行緒B呼叫threadB.interrupt()方法能夠中斷執行緒B的等待過程。

  由於lockInterruptibly()的宣告中丟擲了異常,所以lock.lockInterruptibly()必須放在try塊中或者在呼叫lockInterruptibly()的方法外宣告丟擲InterruptedException。

  因此lockInterruptibly()一般的使用形式如下:

1 2 3 4 5 6 7 8 9 public  void  method()  throws  InterruptedException {      lock.lockInterruptibly();      try  {         //.....      }      finally  {          lock.unlock();      }   }

  注意,當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。因為本身在前面的文章中講過單獨呼叫interrupt()方法不能中斷正在執行過程中的執行緒,只能中斷阻塞過程中的執行緒。

  因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。

  而用synchronized修飾的話,當一個執行緒處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

  2.ReentrantLock

  ReentrantLock,意思是“可重入鎖”,關於可重入鎖的概念在下一節講述。ReentrantLock是唯一實現了Lock介面的類,並且ReentrantLock提供了更多的方法。下面通過一些例項看具體看一下如何使用ReentrantLock。

  例子1,lock()的正確使用方法

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public  class  Test {      private  ArrayList<Integer> arrayList =  new  ArrayList<Integer>();      public  static  void  main(String[] args)  {          final  Test test =  new  Test();                    new  Thread(){              public  void  run() {                  test.insert(Thread.currentThread());              };          }.start();                    new  Thread(){              public  void  run() {                  test.insert(Thread.currentThread());              };          }.start();      }              public  void  insert(Thread thread) {          Lock lock =  new  ReentrantLock();     //注意這個地方          lock.lock();          try  {              System.out.println(thread.getName()+ "得到了鎖" );              for ( int  i= 0 ;i< 5 ;i++) {                  arrayList.add(i);              }          catch  (Exception e) {              // TODO: handle exception          } finally  {              System.out.println(thread.getName()+ "釋放了鎖" );              lock.unlock();          }      } }

   各位朋友先想一下這段程式碼的輸出結果是什麼?

Thread-0得到了鎖
Thread-1得到了鎖
Thread-0釋放了鎖
Thread-1釋放了鎖

  也許有朋友會問,怎麼會輸出這個結果?第二個執行緒怎麼會在第一個執行緒釋放鎖之前得到了鎖?原因在於,在insert方法中的lock變數是區域性變數,每個執行緒執行該方法時都會儲存一個副本,那麼理所當然每個執行緒執行到lock.lock()處獲取的是不同的鎖,所以就不會發生衝突。

  知道了原因改起來就比較容易了,只需要將lock宣告為類的屬性即可。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public  class  Test {      private  ArrayList<Integer> arrayList =  new  ArrayList<Integer>();      private  Lock lock =  new  ReentrantLock();     //注意這個地方      public  static  void  main(String[] args)  {          final  Test test =  new  Test();                    new  Thread(){              public  void  run() {                  test.insert(Thread.currentThread());              };          }.start();                    new  Thread(){              public  void  run() {                  test.insert(Thread.currentThread());              };          }.start();      }              public  void  insert(Thread thread) {          lock.lock();          try  {              System.out.println(thread.getName()+ "得到了鎖" );              for ( int  i= 0 ;i< 5 ;i++) {                  arrayList.add(i);              }          catch  (Exception e) {              // TODO: handle exception          } finally  {              System.out.println(thread.getName()+ "釋放了鎖" );              lock.unlock();          }      } }

   這樣就是正確地使用Lock的方法了。

  例子2,tryLock()的使用方法

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public  class  Test {      private  ArrayList<Integer> arrayList =  new  ArrayList<Integer>();      private  Lock lock =  new  ReentrantLock();     //注意這個地方      public  static  void  main(String[] args)  {          final  Test test =  new  Test();                    new  Thread(){              public  void  run() {                  test.insert(Thread.currentThread());              };          }.start();                    new  Thread(){              public  void  run() {                  test.insert(Thread.currentThread());              };          }.start();      }              public  void  insert(Thread thread) {          if (lock.tryLock()) {              try  {                  System.out.println(thread.getName()+ "得到了鎖" );                  for ( int  i= 0 ;i< 5 ;i++) {                      arrayList.add(i);                  }              catch  (Exception e) {                  // TODO: handle exception              } finally  {                  System.out.println(thread.getName()+ "釋放了鎖" );                  lock.unlock();              }          else  {              System.out.println(thread.getName()+ "獲取鎖失敗" );          }      } }

   輸出結果:

Thread-0得到了鎖
Thread-1獲取鎖失敗
Thread-0釋放了鎖

  例子3,lockInterruptibly()響應中斷的使用方法:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public  class  Test {      private  Lock lock =  new  ReentrantLock();