Java併發分析—Lock

1.Lock 和 Condition

  當使用synchronied進行同步時,能夠在同步代碼塊中只用經常使用的wait和notify等方法,在使用顯示鎖的時候,將經過Condition對象與任意Lock實現組合使用,爲每一個對象提供多個等待方法,其中Lock代替了synchronized方法和語句的使用,Condition代替了Object監視器方法的使用,條件Condition爲線程提供了一個含義,以便在某個狀態條出現可能爲true,另外一個線程通知它以前,一直掛起該線程,即讓其等待,由於訪問該共享狀態信息發生在不一樣的線程中,因此它必須受到保護。html

2.Lock  ReentrantLock

  Lock 接口定義了一組抽象的鎖定操做。與內部鎖定(intrinsic locking)不一樣,Lock 提供了無條件的、可輪詢的、定時的、可中斷的鎖獲取操做,全部加鎖和解鎖的方法都是顯式的。這提供了更加靈活的加鎖機制,彌補了內部鎖在功能上的一些侷限——不能中斷那些正在等待獲取鎖的線程,而且在請求鎖失敗的狀況下,必須無限等待java

  Lock 接口主要定義了下面的一些方法,並經過ReentrantLock實現Lock 接口編程

 1 package com.test;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 import java.util.concurrent.locks.Condition;
 5 import java.util.concurrent.locks.Lock;
 6 
 7 public class LockTest implements Lock{
 8 
 9     /**
10      * lock()用來獲取鎖。若是鎖已被其餘線程獲取,則進行等待
11      */
12     public void lock() {}
13 
14     /**
15      * 經過這個方法去獲取鎖時,若是線程正在等待獲取鎖,則這個線程可以響應中斷,即中斷線程的等待狀態。也就使說,當兩個線程同時經過lock.lockInterruptibly()想獲取某個鎖時,倘若此時線程A獲取到了鎖,而線程B只有在等待,
16      * 那麼對線程B調用threadB.interrupt()方法可以中斷線程B的等待過程。
17      */
18     public void lockInterruptibly() throws InterruptedException {}
19     
20     /**
21      * 表示用來嘗試獲取鎖,若是獲取成功,則返回true,若是獲取失敗(即鎖已被其餘線程獲取),則返回false,
22      * 這個方法不管如何都會當即返回。在拿不到鎖時不會一直在那等待
23      */
24     public boolean tryLock() {
25         return false;
26     }
27     
28     /**
29      * 這個方法在拿不到鎖時會等待必定的時間,在時間期限以內若是還拿不到鎖,就返回false。
30      * 若是若是一開始拿到鎖或者在等待期間內拿到了鎖,則返回true
31      */
32     public boolean tryLock(long time, TimeUnit unit)
33             throws InterruptedException {
34         return false;
35     }
36     
37     /**
38      * 釋放鎖,必須在finally中釋放
39      */
40     public void unlock() {}
41 
42     public Condition newCondition() {
43         return null;
44     }
45 }

  (1)void lock():獲取鎖。若是鎖不可用,出於線程調度目的,將禁用當前線程,而且在得到鎖以前,該線程將一直處於休眠狀態。微信

  例:ide

 1 package com.test;
 2 
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 
 6 public class Main {
 7     Lock lock = new ReentrantLock();
 8     private int i = 0;
 9 
10     public static void main(String[] args) {
11         final Main main = new Main();
12         new Thread(new Runnable() {
13             public void run() {
14                 main.write(Thread.currentThread());
15             }
16         }).start();
17         new Thread(new Runnable() {
18             public void run() {
19                 main.write(Thread.currentThread());
20             }
21         }).start();
22     }
23 
24     public void write(Thread thread) {
25         lock.lock();
26         try {
27             System.out.println(thread.getName() + "獲取了鎖");
28             i = 1;
29         } catch (Exception e) {
30         } finally {
31             lock.unlock();
32             System.out.println(thread.getName() + "釋放了鎖");
33         }
34     }
35 }

  運行結果:函數

Thread-0獲取了鎖
Thread-0釋放了鎖
Thread-1獲取了鎖
Thread-1釋放了鎖

  (2)void lockInterruptibly() throws InterruptedException:若是當前線程未被中斷,則獲取鎖。若是鎖可用,則獲取鎖,並當即返回。若是當前線程在獲取鎖時被 中斷,而且支持對鎖獲取的中斷,則將拋出InterruptedException,並清除當前線程的已中斷狀態。學習

  中斷線程的方法參照: https://www.cnblogs.com/jenkov/p/juc_interrupt.htmlspa

  例:線程

 1 package com.test;
 2 
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 
 6 public class InterruptionInJava extends Thread {
 7     Lock lock = new ReentrantLock();
 8     private volatile static boolean on = false;
 9 
10     public static void main(String[] args) throws InterruptedException {
11         Thread testThread = new Thread(new InterruptionInJava(), "t1");
12         Thread testThread1 = new Thread(new InterruptionInJava(), "t2");
13         testThread.start();
14         testThread1.start();
15         Thread.sleep(1000);
16         InterruptionInJava.on = true;
17         testThread.interrupt();
18     }
19 
20     @Override
21     public void run() {
22         try {
23             test(Thread.currentThread());
24         } catch (InterruptedException e) {
25             e.printStackTrace();
26         }
27     }
28 
29     public void test(Thread thread) throws InterruptedException {
30         lock.lockInterruptibly();
31         try {
32             System.out.println(thread.getName() + "獲取了鎖");
33             while (!on) {
34                 try {
35                     Thread.sleep(10000000);
36                 } catch (InterruptedException e) {
37                     System.out.println(Thread.currentThread().getName()
38                             + "被中斷了");
39                 }
40             }
41         } finally {
42             lock.unlock();
43             System.out.println(thread.getName() + "釋放了鎖");
44         }
45     }
46 }

     運行結果:code

t1獲取了鎖
t2獲取了鎖
t1被中斷了
t1釋放了鎖

  (3)boolean tryLock():若是鎖可用,則獲取鎖,並當即返回值 true。若是鎖不可用,則此方法將當即返回值 false。

  tryLolck()還可以實現可輪詢查詢,若是不能得到全部須要的鎖,則可使用輪詢的獲取方式從新獲取控制權,它會釋放已經得到的控制權,而後從新嘗試。

  例:       

 1 package com.test;
 2 
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 
 6 public class Main {
 7     Lock lock = new ReentrantLock();
 8     private int i = 0;
 9 
10     public static void main(String[] args) {
11         final Main main = new Main();
12         new Thread(new Runnable() {
13             public void run() {
14                 try {
15                     main.write(Thread.currentThread());
16                 } catch (InterruptedException e) {
17                     e.printStackTrace();
18                 }
19             }
20         }).start();
21         new Thread(new Runnable() {
22             public void run() {
23                 try {
24                     main.write(Thread.currentThread());
25                 } catch (InterruptedException e) {
26                     System.out.println(Thread.currentThread().getName()+"線程被中斷了");
27                 }
28             }
29         }).start();
30     }
31 
32     public void write(Thread thread) throws InterruptedException{
33         if(lock.tryLock()) {
34         try {
35             System.out.println(thread.getName() + "獲取了鎖");
36             Thread.sleep(5000);
37         } finally {
38             lock.unlock();
39             System.out.println(thread.getName() + "釋放了鎖");
40         }
41         }else {
42             System.out.println(thread.getName()+"當前鎖不可用");
43         }
44     }
45 }

  運行結果:

Thread-0獲取了鎖
Thread-1當前鎖不可用
Thread-0釋放了鎖

  (4)boolean tryLock(long time, TimeUnit unit) throws InterruptedException:若是鎖在給定的等待時間內空閒,而且當前線程未被中斷,則獲取鎖。

  當使用內部鎖時,一旦開始請求,鎖就不能中止,因此內部鎖實現具備時限的活動帶來了風險,爲了解決這一問題,可以使用定時鎖,當具備時限的活動調用阻塞方法,定時鎖可以在時間預算內設定相應的超時,若是活動在期待的時間內沒能得到結果,定時鎖能使程序提早返回,可定時鎖由boolean tryLock(long time, TimeUnit unit)實現。

  例:    

 1 package com.test;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 import java.util.concurrent.locks.Lock;
 5 import java.util.concurrent.locks.ReentrantLock;
 6 
 7 public class Main {
 8     Lock lock = new ReentrantLock();
 9     private int i = 0;
10 
11     public static void main(String[] args) {
12         final Main main = new Main();
13         new Thread(new Runnable() {
14             public void run() {
15                 try {
16                     main.write(Thread.currentThread());
17                 } catch (InterruptedException e) {
18                     e.printStackTrace();
19                 }
20             }
21         }).start();
22         new Thread(new Runnable() {
23             public void run() {
24                 try {
25                     main.write(Thread.currentThread());
26                 } catch (InterruptedException e) {
27                     System.out.println(Thread.currentThread().getName()+"線程被中斷了");
28                 }
29             }
30         }).start();
31     }
32 
33     public void write(Thread thread) throws InterruptedException{
34         if(lock.tryLock(2000, TimeUnit.MILLISECONDS )) {
35         try {
36             System.out.println(thread.getName() + "獲取了鎖");
37             Thread.sleep(1500);
38         } finally {
39             lock.unlock();
40             System.out.println(thread.getName() + "釋放了鎖");
41         }
42         }else {
43             System.out.println(thread.getName()+"當前鎖不可用");
44         }
45     }
46 } 

  運行結果:

Thread-1獲取了鎖
Thread-1釋放了鎖
Thread-0獲取了鎖
Thread-0釋放了鎖

  (5)void unlock():釋放鎖。

  例:把上例釋放鎖代碼屏蔽掉  

 1 package com.test;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 import java.util.concurrent.locks.Lock;
 5 import java.util.concurrent.locks.ReentrantLock;
 6 
 7 public class Main {
 8     Lock lock = new ReentrantLock();
 9     private int i = 0;
10 
11     public static void main(String[] args) {
12         final Main main = new Main();
13         new Thread(new Runnable() {
14             public void run() {
15                 try {
16                     main.write(Thread.currentThread());
17                 } catch (InterruptedException e) {
18                     e.printStackTrace();
19                 }
20             }
21         }).start();
22         new Thread(new Runnable() {
23             public void run() {
24                 try {
25                     main.write(Thread.currentThread());
26                 } catch (InterruptedException e) {
27                     System.out.println(Thread.currentThread().getName()+"線程被中斷了");
28                 }
29             }
30         }).start();
31     }
32 
33     public void write(Thread thread) throws InterruptedException{
34         if(lock.tryLock(2000, TimeUnit.MILLISECONDS )) {
35         try {
36             System.out.println(thread.getName() + "獲取了鎖");
37             Thread.sleep(1500);
38         } finally {
39             //lock.unlock();
40             //System.out.println(thread.getName() + "釋放了鎖");41         }
42         }else {
43             System.out.println(thread.getName()+"當前鎖不可用");
44         }
45     }
46 }

  運行結果:

Thread-1獲取了鎖
Thread-0當前鎖不可用

  因爲線程1沒有釋放鎖,線程2在獲取鎖時時得不到鎖的。

  (6)Condition newCondition():返回綁定到此 Lock 實例的新 Condition 實

  除以上方法外,ReentrantLock 還增長了一些高級功能,主要有如下3項:

  (1)等待可中斷:當持有鎖的線程長期不釋放鎖時,正在等待的線程能夠選擇放棄等待,改成處理其餘事情,可中斷特性對處理執行時間很是長的同步塊頗有幫助。

  (2)公平鎖:多個線程在等待同一個鎖時必須按照申請鎖的時間順序來依次得到鎖,而非公平鎖不能保證這一點,在鎖被釋放時,任何一個等待鎖的線程都有機會得到鎖,synchronized中的鎖時非公平的,ReentranLock默認狀況下也是非公平的,但能夠經過帶布爾值的構造函數要求使用公平鎖。

  (3)鎖綁定多個條件:指一個ReentrantLock對象能夠同時綁定多個Condition對象,er在synchronized中,鎖對象的wait()和notify()或notifyAll()方法能夠實現一個隱含的條件,若是和多餘一個的條件關聯的時候,就不得不額外添加一個鎖,而ReentrantLock則無需這樣作,只須要屢次調用newCondition()方法便可。

  例,調用 Condition.await() 將在等待前以原子方式釋放鎖,並在等待返回前從新獲取鎖。

  ReentrantLock 實現了Lock 接口。得到ReentrantLock 的鎖與進入synchronized塊具備相同的語義,釋放 ReentrantLock 鎖與退出synchronized 塊有相同的語義。相比於 synchronized,ReentrantLock 提供了更多的靈活性來處理不可用的鎖。

3.編程時鎖的選擇

  1.最好既不是用 Lock/Condition 也不使用  synchronized關鍵字在許多狀況下,可使用java.util.concurrent包中的一種機制,它會處理全部的加鎖。

  2.若是synchronized 關鍵字適合編寫的程序,那就儘可能使用它,這樣能夠減小編寫的代碼數量,減小出錯的概率,若是特別須要Lock/Condition 結構提供的獨有的特性時,纔是用Lock/Condition 。

參考文獻:

1. http://www.javashuo.com/article/p-vdqwlxdz-hk.html

2.https://www.cnblogs.com/kylindai/archive/2006/01/24/322667.html

 3.不明來歷pdf文檔《Java鎖機制詳解》,若有侵權,請聯繫LZ。

 

         歡迎掃碼關注個人微信公衆號,或者微信公衆號直接搜索Java傳奇,不定時更新一些學習筆記!

                                                       

相關文章
相關標籤/搜索