被synchronized修飾了,當一個線程獲取了對應的鎖,並執行該代碼塊時,其他線程便只能一直等待,等待獲取鎖的線程釋放鎖,獲取線程被阻塞時,沒有釋放鎖會導致等待線程無期限的等待下去。另外,多個線程都只是進行讀操作時,線程之間不會發生沖突,通過Lock就可以辦到。Lock還可以知道線程有沒有成功獲取到鎖。
1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問;
2)Lock和synchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。
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 @Override 13 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類的幾個屬性:
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 @Override 16 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 exception 40 } 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 exception 55 } 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 exception 23 } finally{ 24 /** 25 * 將寫鎖降級為讀鎖 26 */ 27 readLock.lock(); 28 writeLock.unlock(); 29 } 30 } 31 } catch (Exception e) { 32 // TODO: handle exception 33 } finally{ 34 readLock.unlock(); 35 } 36 37 return value; 38 } 39 }
參考資料:
http://www.cnblogs.com/dolphin0520/p/3923167.html
javaAPI