Java锁详解之ReentrantLock
文章目錄
- 寫在前面
- ReentrantLock的重要方法
- ReentrantLock使用示例
- ReentrantLock的公平和非公平鎖
- ReentrantLock的重入鎖
- ReentrantLock的Condition機制
- ReentrantLock與synchronized(內部鎖)性能比較
- Lock與synchronized區別
寫在前面
ReentrantLock繼承了AbstractQueuedSynchronizer(簡稱AQS),AQS用到了模板模式這種設計模式,所以閱讀AQS和ReentrantLock的源碼時,最好對模板設計模式有一定了解,這里我就講AQS了。
ReentrantLock是除了synchronized用得較多的一種鎖。ReentrantLock也屬于重入鎖,后面接著就會提到它的重入鎖實現原理。
ReentrantLock的功能要比內部鎖synchronized更多,如指定鎖等待時間的方法tryLock(long time,TimeUnit unit)、中斷鎖的方法lockInterruptibly()、沒獲取鎖直接返回的方法tryLock()。
所以,什么時候選擇ReentrantLock呢?
一般是synchronized 不滿足需求時,才選擇ReentrantLock,比如實現立即返回、可中斷、conditon機制時
ReentrantLock的重要方法
| lock() | 獲取鎖。如果鎖已經被占用,則等待 |
| tryLock() | 嘗試獲取鎖,拿到鎖返回true,沒拿到返回false,并立即返回 |
| tryLock(long time, TimeUnit unit) | 在指定時間內會等待獲取鎖,如果一直拿不到就返回false,并立即返回。在等待過程中可以進行中斷 |
| lockInterruptibley() | 獲取鎖。如果線程interrupted了,則跟著拋出異常中斷 |
| unLock() | 釋放鎖 |
| newCondition() | 創建一個與此 Lock 實例一起使用的 Condition 實例。 |
ReentrantLock使用示例
private ReentrantLock lock = new ReentrantLock();public void run() {// 加鎖 if(lock.tryLock()){System.out.println(Thread.currentThread().getName()+" 拿到鎖");try {Thread.sleep(3000);// doSomething...} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();// 釋放鎖}} else{System.out.println(Thread.currentThread().getName()+" 獲取鎖失敗");}}ReentrantLock的公平和非公平鎖
公平鎖:公平鎖講究先來先到,線程在獲取鎖時,會先看這個鎖的等待隊列中是否有線程在等待,如果有,則當前線程就會直接進入等待隊列中,而不是直接去搶占鎖。
ReentrantLock fairLock = new ReentrantLock(true); // 初始化一個公平鎖非公平鎖:不管是否有等待隊列,先直接嘗試獲取鎖,如果拿到鎖,則立刻占有鎖對象;如果未拿到,則自動排到隊尾等待。
ReentrantLock fairLock = new ReentrantLock(); // 初始化一個非公平鎖 ReentrantLock fairLock = new ReentrantLock(false); // 初始化一個非公平鎖非公平鎖的性能比公平鎖的性能好很多,所以ReentrantLock默認是非公平鎖。
為什么非公平鎖性能好?
//公平鎖靜態內部類 static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// hasQueuedPredecessors返回true,說明當前節點不是頭節點或隊列不為空,此時直接加在隊列后面// hasQueuedPredecessors前面有個非符號,此時不再走&&后面的CAS操作。// hasQueuedPredecessors返回false,說明當前節點是頭節點或隊列為空,// 說明只有當前線程在競爭鎖,此時可以進行compareAndSetState操作。if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}}// 非公平鎖靜態內部類 static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires); // 是Sync的方法}}// Sync的方法 final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {// 重入鎖機制int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false; }看了ReentrantLock的公平鎖和非公平鎖的源代碼你就會發現,公平鎖直接走的父類AbstractQueuedSynchronizer的acquire方法,而非公平鎖是先作CAS操作。非公平鎖這樣做的優點是:
1.如果直接拿到了鎖,就避免了維護node鏈表隊列
2.如果直接拿到了鎖,就避免了線程休眠和喚醒的上下文切換
ReentrantLock的重入鎖
ReentrantLock它是怎么實現重入鎖的呢?
截取前面小節的部分代碼:
從代碼可以看出,來獲取鎖的線程如果是當前占有鎖的線程,則直接將nextc+1。而且從if (nextc < 0)知道,可重入的次數是int的最大值。剛入門的同學可能不知道為什么會是int的最大的值,這是因為一個int值在做不限制累加,到了最大值2147483647時會溢出變成負數,這個在大學計算機相關課程應該會講到。
ReentrantLock的Condition機制
ReentrantLock還提供了Condition機制來進行復雜的線程控制功能。
Condition是在java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現線程間的協作,相比使用Object的wait()、notify(),使用Condition的await()、signal()這種方式實現線程間協作更加安全和高效。因此通常來說比較推薦使用Condition。
ArrayBlockingQueue的實現就依靠了Condition機制。如下核心代碼。
下面代碼中Condition機制的核心原理就是:當前線程被哪個condtion阻塞(調用await),就會加到當前condition的阻塞隊列里。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock;/*** 數組阻塞隊列** @param <E>*/ class ArrayBlockingQueueDemon<E> {final ReentrantLock lock;/*** put元素時被阻塞的條件*/final Condition putCondition;/*** take數據時被阻塞的條件*/final Condition takeCondition;/*** 放元素的隊列*/final Object[] items;/*** take下一個元素的索引下標*/int takeIndex;/*** put下一個元素的索引下標*/int putIndex;/*** 隊列中元素個數*/int count;/*** 構造方法** @param capacity 允許隊列* @param fair 是否創建公平鎖*/public ArrayBlockingQueueDemon(int capacity, boolean fair) {if (capacity <= 0) {throw new IllegalArgumentException();}this.items = new Object[capacity];lock = new ReentrantLock(fair);takeCondition = lock.newCondition();putCondition = lock.newCondition();}public void put(E e) throws InterruptedException {if (e == null) {throw new NullPointerException();}lock.lockInterruptibly();try {// 隊列到達初始化上限時,不再允許向隊列放數據,放數據的線程要等待// 此刻,當前線程會被添加到putCondition的阻塞隊列里while (count == items.length) {putCondition.await();}// 入隊enqueue(e);} finally {lock.unlock();}}public E take() throws InterruptedException {lock.lockInterruptibly();try {// 當隊列沒有數據時,拿數據的線程等待// 此該,當前線程會被添加到takeCondition的阻塞隊列里while (count == 0) {takeCondition.await();}// 出隊并返回return dequeue();} finally {lock.unlock();}}private void enqueue(E x) {items[putIndex] = x;++putIndex;if (putIndex == items.length) {putIndex = 0;}count++;// 隊列里增加元素了,可以喚醒取元素的線程了takeCondition.signal();}private E dequeue() {E x = (E) items[takeIndex];items[takeIndex] = null;++takeIndex;if (takeIndex == items.length) {takeIndex = 0;}count--;// 隊列元素減少了,騰出了位置,可喚醒放元素的線程了putCondition.signal();return x;}}注意:
Condition在使用之前,一定要先獲取監視器。即調用Condition的await()和signal()方法的代碼,都必須在lock.lock()和lock.unlock之間。
Conditon中的await()對應Object的wait(),Condition中的signal()對應Object的notify(),Condition中的signalAll()對應Object的notifyAll()。
ReentrantLock與synchronized(內部鎖)性能比較
在資源競爭不激烈的情形下,ReentrantLock性能稍微比synchronized差一點。但是當同步非常激烈的時候,synchronized的性能會下降好幾十倍。而ReentrantLock不會有太大的性能波動。
在寫同步的時候,優先考慮synchronized,畢竟synchronized更簡單,總得來說性能被優化得還不錯。ReentrantLock比較復雜,寫得不好,還可能會給程序性能帶來大問題。
Lock與synchronized區別
總結
以上是生活随笔為你收集整理的Java锁详解之ReentrantLock的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL拆分实现与注意事项
- 下一篇: Java锁详解之改进读写锁Stamped