java並發庫--鎖,java並發--
被synchronized修飾了,當一個線程擷取了對應的鎖,並執行該代碼塊時,其他線程便只能一直等待,等待擷取鎖的線程釋放鎖,擷取線程被阻塞時,沒有釋放鎖會導致等待線程無期限的等待下去。另外,多個線程都只是進行讀操作時,線程之間不會發生衝突,通過Lock就可以辦到。Lock還可以知道線程有沒有成功擷取到鎖。
1)Lock不是Java語言內建的,synchronized是Java語言的關鍵字,因此是內建特性。Lock是一個類,通過這個類可以實現同步訪問;
2)Lock和synchronized有一點非常大的不同,採用synchronized不需要使用者去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的佔用;而Lock則必須要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死結現象。
- java.util.concurrent.locks包下常用的類
1)Lock
lock介面的原始碼
1 public interface Lock {2 void lock();3 void lockInterruptibly() throws InterruptedException;4 boolean tryLock();5 boolean tryLock(long time, TimeUnit unit) throws InterruptedException;6 void unlock();7 Condition newCondition();8 }
方法詳細介紹參考API。
2)ReentrantLock
一個可重新進入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行為和語義,但功能更強大。
執行個體:
1 public class LockTest { 2 public static void main(String[] args) { 3 final Output output = new Output(); 4 while(true){ 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 output.output("love"); 9 }10 }).start();11 new Thread(new Runnable() {12 @Override13 public void run() {14 output.output("hate");15 }16 }).start();17 }18 19 }20 21 } 22 class Output{23 Lock lock = new ReentrantLock();24 public void output(String str){25 lock.lock();26 try {27 for (int i = 0; i < str.length(); i++) {28 System.out.print(str.charAt(i));29 }30 System.out.println();31 } catch (Exception e) {32 e.printStackTrace();33 } finally{34 lock.unlock();35 }36 37 38 }39 }
3)ReadWriteLock
ReadWriteLock介面原始碼,它只提供了擷取寫入鎖和讀取鎖的方法
1 public interface ReadWriteLock { 2 /** 3 * Returns the lock used for reading. 4 * 5 * @return the lock used for reading. 6 */ 7 Lock readLock(); 8 9 /**10 * Returns the lock used for writing.11 *12 * @return the lock used for writing.13 */14 Lock writeLock();15 }
一個用來擷取讀鎖,一個用來擷取寫鎖。也就是說將檔案的讀寫操作分開,分成2個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。
4)ReentrantReadWriteLock
ReentrantReadWriteLock類的幾個屬性:
- 擷取順序:此類不會將讀取者優先或寫入者優先強加給鎖訪問的排序。但是,它確實支援可選的公平 策略。
- 重入:此鎖允許 reader 和 writer 按照
ReentrantLock 的樣式重新擷取讀取鎖或寫入鎖。在寫入線程保持的所有寫入鎖都已經釋放後,才允許重入 reader 使用它們。此外,writer 可以擷取讀取鎖,但反過來則不成立。在其他應用程式中,當在調用或回調那些在讀取鎖狀態下執行讀取操作的方法期間保持寫入鎖時,重入很有用。如果 reader 試圖擷取寫入鎖,那麼將永遠不會獲得成功。
- 鎖降級:重入還允許從寫入鎖降級為讀取鎖,其實現方式是:先擷取寫入鎖,然後擷取讀取鎖,最後釋放寫入鎖。但是,從讀取鎖定擴大到寫入鎖是不可能的。
- 鎖擷取的中斷:讀取鎖和寫入鎖都支援鎖擷取期間的中斷。
執行個體:分別用三個線程進行讀取和寫入資料。
1 public class ReadWriteLockTest { 2 public static void main(String[] args) { 3 final Date date = new Date(); 4 for (int i = 0; i < 3; i++) { 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 while(true){ 9 date.put();10 }11 }12 }).start();13 new Thread(new Runnable() {14 15 @Override16 public void run() {17 while(true){18 date.get();19 }20 }21 }).start();22 }23 24 }25 }26 27 class Date{28 29 ReadWriteLock rwl = new ReentrantReadWriteLock();30 31 private Integer date;32 33 public void get(){34 rwl.readLock().lock();35 try {36 System.out.println(Thread.currentThread().getName()+"準備讀取資料");37 System.out.println(Thread.currentThread().getName()+"讀取資料:"+date);38 } catch (Exception e) {39 // TODO: handle exception40 } finally{41 rwl.readLock().unlock();42 }43 44 }45 46 public void put(){47 rwl.writeLock().lock();48 try {49 System.out.println(Thread.currentThread().getName()+"準備寫入資料");50 Thread.sleep(50);51 date = new Random().nextInt(5000);52 System.out.println(Thread.currentThread().getName()+"寫入資料:"+date);53 } catch (Exception e) {54 // TODO: handle exception55 } finally{56 rwl.writeLock().unlock();57 }58 }59 }
這樣子,讀中有讀,讀中沒有寫,寫中也沒有寫,大大提升了讀操作的效率。
5)Lock和synchronized的選擇
總結來說,Lock和synchronized有以下幾點不同:
1)Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內建的語言實現;
2)synchronized在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死結現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死結現象,因此使用Lock時需要在finally塊中釋放鎖;
3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;
4)通過Lock可以知道有沒有成功擷取鎖,而synchronized卻無法辦到。
5)Lock可以提高多個線程進行讀操作的效率。
在效能上來說,如果競爭資源不激烈,兩者的效能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的效能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。
1)可重新進入鎖
如果鎖具備可重新進入性,則稱作為可重新進入鎖。像synchronized和ReentrantLock都是可重新進入鎖,可重新進入性在我看來實際上表明了鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。舉個簡單的例子,當一個線程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。
1 class MyClass {2 public synchronized void method1() {3 method2();4 }5 public synchronized void method2() {6 7 }8 }
上述代碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,線程A執行到了method1,此時線程A擷取了這個對象的鎖,而由於method2也是synchronized方法,假如synchronized不具備可重新進入性,此時線程A需要重新申請鎖。但是這就會造成一個問題,因為線程A已經持有了該對象的鎖,而又在申請擷取該對象的鎖,這樣就會線程A一直等待永遠不會擷取到的鎖。而由於synchronized和Lock都具備可重新進入性,所以不會發生上述現象。
2)可中斷鎖
可中斷鎖:顧名思義,就是可以響應中斷的鎖。
在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
如果某一線程A正在執行鎖中的代碼,另一線程B正在等待擷取該鎖,可能由於等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。
在前面示範lockInterruptibly()的用法時已經體現了Lock的可中斷性。
3)公平鎖
公平鎖即盡量以請求鎖的順序來擷取鎖。比如同是有多個線程在等待一個鎖,當這個鎖被釋放時,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖。
非公平鎖即無法保證鎖的擷取是按照請求鎖的順序進行的。這樣就可能導致某個或者一些線程永遠擷取不到鎖。
在Java中,synchronized就是非公平鎖,它無法保證等待的線程擷取鎖的順序。
而對於ReentrantLock和ReentrantReadWriteLock,它預設情況下是非公平鎖,但是可以設定為公平鎖。
4)讀寫鎖
讀寫鎖將對一個資源(比如檔案)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。
正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發生衝突。
ReadWriteLock就是讀寫鎖,它是一個介面,ReentrantReadWriteLock實現了這個介面。
可以通過readLock()擷取讀鎖,通過writeLock()擷取寫鎖。
緩衝一般涉及到讀寫,那麼就可以應用鎖來實現緩衝系統,下面將實現緩衝系統的核心代碼:
1 public class CacheDemo { 2 3 private Map<String, Object> map = new HashMap<String, Object>();//緩衝map 4 private ReadWriteLock rwl = new ReentrantReadWriteLock(); 5 private ReadLock readLock = (ReadLock) rwl.readLock();//讀鎖 6 private WriteLock writeLock = (WriteLock) rwl.writeLock();//寫鎖 7 8 public Object getValue(String key){ 9 readLock.lock();10 Object value = null;11 try {12 value = map.get(key);13 if(value == null){//如果對象為空白,讀鎖釋放14 readLock.unlock();15 writeLock.lock();//擷取寫鎖,保證同步16 try {17 if(value == null){//再次判斷對象是否為空白,防止多線程訪問重複寫入,影響效率18 value = "abcd";//擷取快取資料19 }20 map.put(key, value);21 } catch (Exception e) {22 // TODO: handle exception23 } finally{24 /**25 * 將寫鎖降級為讀鎖26 */27 readLock.lock();28 writeLock.unlock();29 }30 }31 } catch (Exception e) {32 // TODO: handle exception33 } finally{34 readLock.unlock();35 }36 37 return value;38 }39 }
參考資料:
http://www.cnblogs.com/dolphin0520/p/3923167.html
javaAPI