ReentrantLock(重入鎖)功能詳解和應用演示
1. ReentrantLock簡介
jdk中獨佔鎖的實現除了使用關鍵字synchronized外,還可以使用ReentrantLock。雖然在效能上ReentrantLock和synchronized沒有什麼區別,但ReentrantLock相比synchronized而言功能更加豐富,使用起來更為靈活,也更適合複雜的併發場景。
2. ReentrantLock和synchronized的相同點
2.1 ReentrantLock是獨佔鎖且可重入的
例子
public class ReentrantLockTest { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); for (int i = 1; i上面的程式碼透過
lock()
方法先獲取鎖三次,然後透過unlock()
方法釋放鎖3次,程式可以正常退出。從上面的例子可以看出,ReentrantLock是可以重入的鎖,當一個執行緒獲取鎖時,還可以接著重複獲取多次。在加上ReentrantLock的的獨佔性,我們可以得出以下ReentrantLock和synchronized的相同點。
1.ReentrantLock和synchronized都是獨佔鎖,只允許執行緒互斥的訪問臨界區。但是實現上兩者不同:synchronized加鎖解鎖的過程是隱式的,使用者不用手動操作,優點是操作簡單,但顯得不夠靈活。一般併發場景使用synchronized的就夠了;ReentrantLock需要手動加鎖和解鎖,且解鎖的操作儘量要放在finally程式碼塊中,保證執行緒正確釋放鎖。ReentrantLock操作較為複雜,但是因為可以手動控制加鎖和解鎖過程,在複雜的併發場景中能派上用場。
2.ReentrantLock和synchronized都是可重入的。synchronized因為可重入因此可以放在被遞迴執行的方法上,且不用擔心執行緒最後能否正確釋放鎖;而ReentrantLock在重入時要卻確保重複獲取鎖的次數必須和重複釋放鎖的次數一樣,否則可能導致其他執行緒無法獲得該鎖。
2. ReentrantLock相比synchronized的額外功能
2.1 ReentrantLock可以實現公平鎖。
公平鎖是指當鎖可用時,在鎖上等待時間最長的執行緒將獲得鎖的使用權。而非公平鎖則隨機分配這種使用權。和synchronized一樣,預設的ReentrantLock實現是非公平鎖,因為相比公平鎖,非公平鎖效能更好。當然公平鎖能防止飢餓,某些情況下也很有用。在建立ReentrantLock的時候透過傳進引數true
建立公平鎖,如果傳入的是false
或沒傳引數則建立的是非公平鎖
ReentrantLock lock = new ReentrantLock(true);
繼續跟進看下原始碼
/** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
可以看到公平鎖和非公平鎖的實現關鍵在於成員變數sync
的實現不同,這是鎖實現互斥同步的核心。以後有機會我們再細講。
一個公平鎖的例子
public class ReentrantLockTest { static Lock lock = new ReentrantLock(true); public static void main(String[] args) throws InterruptedException { for(int i=0;i
公平鎖結果
我們開啟5個執行緒,讓每個執行緒都獲取釋放鎖兩次。為了能更好的觀察到結果,在每次獲取鎖前讓執行緒休眠10毫秒。可以看到執行緒幾乎是輪流的獲取到了鎖。如果我們改成非公平鎖,再看下結果
非公平鎖結果
執行緒會重複獲取鎖。如果申請獲取鎖的執行緒足夠多,那麼可能會造成某些執行緒長時間得不到鎖。這就是非公平鎖的“飢餓”問題。
公平鎖和非公平鎖該如何選擇
大部分情況下我們使用非公平鎖,因為其效能比公平鎖好很多。但是公平鎖能夠避免執行緒飢餓,某些情況下也很有用。
2.2 .ReentrantLock可中斷響應
當使用synchronized實現鎖時,阻塞在鎖上的執行緒除非獲得鎖否則將一直等待下去,也就是說這種無限等待獲取鎖的行為無法被中斷。而ReentrantLock給我們提供了一個可以響應中斷的獲取鎖的方法lockInterruptibly()
。該方法可以用來解決死鎖問題。
響應中斷的例子
public class ReentrantLockTest { static Lock lock1 = new ReentrantLock(); static Lock lock2 = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new ThreadDemo(lock1, lock2));//該執行緒先獲取鎖1,再獲取鎖2 Thread thread1 = new Thread(new ThreadDemo(lock2, lock1));//該執行緒先獲取鎖2,再獲取鎖1 thread.start(); thread1.start(); thread.interrupt();//是第一個執行緒中斷 } static class ThreadDemo implements Runnable { Lock firstLock; Lock secondLock; public ThreadDemo(Lock firstLock, Lock secondLock) { this.firstLock = firstLock; this.secondLock = secondLock; } @Override public void run() { try { firstLock.lockInterruptibly(); TimeUnit.MILLISECONDS.sleep(10);//更好的觸發死鎖 secondLock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } finally { firstLock.unlock(); secondLock.unlock(); System.out.println(Thread.currentThread().getName()+"正常結束!"); } } } }
結果
構造死鎖場景:建立兩個子執行緒,子執行緒在執行時會分別嘗試獲取兩把鎖。其中一個執行緒先獲取鎖1在獲取鎖2,另一個執行緒正好相反。如果沒有外界中斷,該程式將處於死鎖狀態永遠無法停止。我們透過使其中一個執行緒中斷,來結束執行緒間毫無意義的等待。被中斷的執行緒將丟擲異常,而另一個執行緒將能獲取鎖後正常結束。
2.3 獲取鎖時限時等待
ReentrantLock還給我們提供了獲取鎖限時等待的方法tryLock()
,可以選擇傳入時間引數,表示等待指定的時間,無參則表示立即返回鎖申請的結果:true表示獲取鎖成功,false表示獲取鎖失敗。我們可以使用該方法配合失敗重試機制來更好的解決死鎖問題。
更好的解決死鎖的例子
public class ReentrantLockTest { static Lock lock1 = new ReentrantLock(); static Lock lock2 = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new ThreadDemo(lock1, lock2));//該執行緒先獲取鎖1,再獲取鎖2 Thread thread1 = new Thread(new ThreadDemo(lock2, lock1));//該執行緒先獲取鎖2,再獲取鎖1 thread.start(); thread1.start(); } static class ThreadDemo implements Runnable { Lock firstLock; Lock secondLock; public ThreadDemo(Lock firstLock, Lock secondLock) { this.firstLock = firstLock; this.secondLock = secondLock; } @Override public void run() { try { while(!lock1.tryLock()){ TimeUnit.MILLISECONDS.sleep(10); } while(!lock2.tryLock()){ lock1.unlock(); TimeUnit.MILLISECONDS.sleep(10); } } catch (InterruptedException e) { e.printStackTrace(); } finally { firstLock.unlock(); secondLock.unlock(); System.out.println(Thread.currentThread().getName()+"正常結束!"); } } } }
結果
執行緒透過呼叫tryLock()
方法獲取鎖,第一次獲取鎖失敗時會休眠10毫秒,然後重新獲取,直到獲取成功。第二次獲取失敗時,首先會釋放第一把鎖,再休眠10毫秒,然後重試直到成功為止。執行緒獲取第二把鎖失敗時將會釋放第一把鎖,這是解決死鎖問題的關鍵,避免了兩個執行緒分別持有一把鎖然後相互請求另一把鎖。
3. 結合Condition實現等待通知機制
使用synchronized結合Object上的wait和notify方法可以實現執行緒間的等待通知機制。ReentrantLock結合Condition介面同樣可以實現這個功能。而且相比前者使用起來更清晰也更簡單。
3.1 Condition使用簡介
Condition由ReentrantLock物件建立,並且可以同時建立多個
static Condition notEmpty = lock.newCondition();static Condition notFull = lock.newCondition();
Condition介面在使用前必須先呼叫ReentrantLock的lock()方法獲得鎖。之後呼叫Condition介面的await()將釋放鎖,並且在該Condition上等待,直到有其他執行緒呼叫Condition的signal()方法喚醒執行緒。使用方式和wait,notify類似。
一個使用condition的簡單例子
public class ConditionTest { static ReentrantLock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { lock.lock(); new Thread(new SignalThread()).start(); System.out.println("主執行緒等待通知"); try { condition.await(); } finally { lock.unlock(); } System.out.println("主執行緒恢復執行"); } static class SignalThread implements Runnable { @Override public void run() { lock.lock(); try { condition.signal(); System.out.println("子執行緒通知"); } finally { lock.unlock(); } } } }
執行結果
3.2 使用Condition實現簡單的阻塞佇列
阻塞佇列是一種特殊的先進先出佇列,它有以下幾個特點
1.入隊和出隊執行緒安全
2.當佇列滿時,入隊執行緒會被阻塞;當佇列為空時,出隊執行緒會被阻塞。
阻塞佇列的簡單實現
public class MyBlockingQueue{ int size;//阻塞佇列最大容量 ReentrantLock lock = new ReentrantLock(); LinkedList list=new LinkedList();//佇列底層實現 Condition notFull = lock.newCondition();//佇列滿時的等待條件 Condition notEmpty = lock.newCondition();//佇列空時的等待條件 public MyBlockingQueue(int size) { this.size = size; } public void enqueue(E e) throws InterruptedException { lock.lock(); try { while (list.size() ==size)//佇列已滿,在notFull條件上等待 notFull.await(); list.add(e);//入隊:加入連結串列末尾 System.out.println("入隊:" +e); notEmpty.signal(); //通知在notEmpty條件上等待的執行緒 } finally { lock.unlock(); } } public E dequeue() throws InterruptedException { E e; lock.lock(); try { while (list.size() == 0)//佇列為空,在notEmpty條件上等待 notEmpty.await(); e = list.removeFirst();//出隊:移除連結串列首元素 System.out.println("出隊:"+e); notFull.signal();//通知在notFull條件上等待的執行緒 return e; } finally { lock.unlock(); } } }
測試程式碼
public static void main(String[] args) throws InterruptedException { MyBlockingQueuequeue = new MyBlockingQueue(2); for (int i = 0; i
執行結果
4. 總結
ReentrantLock是可重入的獨佔鎖。比起synchronized功能更加豐富,支援公平鎖實現,支援中斷響應以及限時等待等等。可以配合一個或多個Condition條件方便的實現等待通知機制。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4422/viewspace-2804877/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- ReentrantLock可重入鎖——原始碼詳解ReentrantLock原始碼
- Java 重入鎖 ReentrantLock 原理分析JavaReentrantLock
- Lock介面、重入鎖ReentrantLock、讀寫鎖ReentrantReadWriteLockReentrantLock
- Java併發包原始碼學習系列:ReentrantLock可重入獨佔鎖詳解Java原始碼ReentrantLock
- 從原始碼入手詳解ReentrantLock,一個比synchronized更強大的可重入鎖原始碼ReentrantLocksynchronized
- ReentrantLock可重入鎖、公平鎖非公平鎖區別與實現原理ReentrantLock
- Java併發程式設計之鎖機制之(ReentrantLock)重入鎖Java程式設計ReentrantLock
- 死鎖和可重入鎖
- java高併發系列 - 第12天JUC:ReentrantLock重入鎖JavaReentrantLock
- 【Java】深入理解ReentrantLock可重入鎖之簡單使用JavaReentrantLock
- 【java併發程式設計】ReentrantLock 可重入讀寫鎖Java程式設計ReentrantLock
- java重入鎖、公平鎖和非公平鎖Java
- ReentrantLock詳解ReentrantLock
- 可重入鎖與不可重入鎖理解
- 可重入鎖
- 從ReentrantLock加鎖解鎖角度分析AQSReentrantLockAQS
- java併發程式設計 | 鎖詳解:AQS,Lock,ReentrantLock,ReentrantReadWriteLockJava程式設計AQSReentrantLock
- 意向共享鎖與意向排它鎖:詳解與應用
- 詳解OpenCV For Java環境搭建與功能演示OpenCVJava
- ReentrantLock可重入、可打斷、Condition原理剖析ReentrantLock
- 重入鎖的理解
- Android PathMeasure詳解和應用Android
- Nginx超詳細常用功能演示,夠用啦~~~Nginx
- redis分散式鎖-可重入鎖Redis分散式
- AQS原始碼深入分析之獨佔模式-ReentrantLock鎖特性詳解AQS原始碼模式ReentrantLock
- Lock鎖之重入鎖與讀寫鎖
- synchronized鎖重入問題synchronized
- Java 讀寫鎖 ReadWriteLock 原理與應用場景詳解Java
- 小米MIUI系統即將上線新功能:應用鎖支援人臉解鎖UI
- 詳解Python中sys模組的功能與應用Python
- 可重入鎖原始碼分析原始碼
- ZooKeeper 分散式鎖 Curator 原始碼 02:可重入鎖重複加鎖和鎖釋放分散式原始碼
- Java鎖之ReentrantLock(一)JavaReentrantLock
- Java鎖之ReentrantLock(二)JavaReentrantLock
- Java中的ReentrantLock鎖JavaReentrantLock
- 面經手冊 · 第16篇《碼農會鎖,ReentrantLock之公平鎖講解和實現》ReentrantLock
- border-sizing屬性詳解和應用
- 淺談Java中的鎖:Synchronized、重入鎖、讀寫鎖Javasynchronized