1.JAVA多線程(十五)Java多線程之ReentrantLock重入鎖
1.1 什麼是ReentrantLock重入鎖?
重入鎖:自己可以再次獲取自己的內部鎖。比如一個線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當其再次想要獲取這個對象的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。同一個線程每次獲取鎖,鎖的計數器都自增1,所以要等到鎖的計數器下降爲0時才能釋放鎖。
1.2 synchronized和ReentrantLock 的區別
- 兩者都是可重入鎖。
- synchronized 依賴於 JVM 而 ReentrantLock 依賴於 API
synchronized 是依賴於 JVM 實現的,前面我們也講到了 虛擬機團隊在 JDK1.6 爲 synchronized 關鍵字進行了很多優化,但是這些優化都是在虛擬機層面實現的,並沒有直接暴露給我們。ReentrantLock 是 JDK 層面實現的(也就是 API 層面,需要 lock() 和 unlock() 方法配合 try/finally 語句塊來完成),所以我們可以通過查看它的源代碼,來看它是如何實現的。 - ReentrantLock 比 synchronized 增加了一些高級功能:
-
等待可中斷:
ReentrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現這個機制。也就是說正在等待的線程可以選擇放棄等待,改爲處理其他事情。 -
可實現公平鎖:
ReentrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。 ReentrantLock默認情況是非公平的,可以通過 ReentrantLock類的ReentrantLock(boolean fair)構造方法來制定是否是公平的 -
可實現選擇性通知(鎖可以綁定多個條件)
synchronized關鍵字與wait()和notify()/notifyAll()方法相結合可以實現等待/通知機制,ReentrantLock類當然也可以實現,但是需要藉助於Condition接口與newCondition() 方法。Condition是JDK1.5之後纔有的,它具有很好的靈活性,比如可以實現多路通知功能也就是在一個Lock對象中可以創建多個Condition實例(即對象監視器),線程對象可以註冊在指定的Condition中,從而可以有選擇性的進行線程通知,在調度線程上更加靈活。 在使用notify()/notifyAll()方法進行通知時,被通知的線程是由 JVM 選擇的,用ReentrantLock類結合Condition實例可以實現“選擇性通知” ,這個功能非常重要,而且是Condition接口默認提供的。而synchronized關鍵字就相當於整個Lock對象中只有一個Condition實例,所有的線程都註冊在它一個身上。如果執行notifyAll()方法的話就會通知所有處於等待狀態的線程這樣會造成很大的效率問題,而Condition實例的signalAll()方法 只會喚醒註冊在該Condition實例中的所有等待線程。
- 兩者都是可重入鎖
JDK5中,synchronized是性能低效的,因爲這是一個重量級操作,對性能的最大影響是阻塞的實現,掛起線程和恢復線程的操作,都需要轉入內核態中完成,給併發帶來了很大壓力。
JDK6中synchronized加入了自適應自旋、鎖消除、鎖粗化、輕量級鎖、偏向鎖等一系列優化,官方也支持synchronized,提倡在synchronized能實現需求的前提下,優先考慮synchronized來進行同步。
1.3 ReentrantLock使用樣例
package com.yuanxw.chapter15;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;
/**
* ReentrantLock重入鎖
*/
public class ReentrantLockExample {
private static final Lock LOCK = new ReentrantLock();
public static void main(String[] args) {
// 創建兩個線程
IntStream.range(0, 2).forEach(i -> {
new Thread(() -> testReentrantLock()).start();
});
}
public static void testReentrantLock() {
try {
LOCK.lock();
System.out.println(String.format("線程【%s】工作--->開始", Thread.currentThread().getName()));
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
System.out.println(String.format("線程【%s】工作--->結束", Thread.currentThread().getName()));
}
}
}
執行結果:
線程【Thread-0】工作--->開始
線程【Thread-0】工作--->結束
線程【Thread-1】工作--->開始
線程【Thread-1】工作--->結束
1.4 ReentrantLock源碼
- 構造方法,我們可以看出默認的無參是非公平鎖,有參構造true表示公平,false表示非公平。lock()獲取鎖,其實就是把state從0變成n(重入鎖可以累加)。
實際調用的是sync的lock方法,分公平和非公平。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* 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();
}
- 公平實現:FairSync,我們發現其實調用的是acquire,其實這個是AQS的acquire,然後aqs的acquire的方法裏面又會調用tryAcquire方法,因爲這個方法需要同步組件自己去實現,所以ReentrantLock裏面重寫了AQS的tryAcquire方法,所以我們獲取到鎖就會返回true,沒有就會返回false;然後沒有獲取到鎖的線程就交給AQS去處理。
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
// 獲取當前的線程
final Thread current = Thread.currentThread();
// 獲取鎖的狀態
int c = getState();
if (c == 0) {
/**
* hasQueuedPredecessors 判斷隊列還有沒有其它node,要保證公平
* 沒有在用cas設置狀態
**/
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;
}
}
- 非公平實現:NonfairSync,我們可以發現基本和公平一樣,就沒有hasQueuedPredecessors方法,沒有遵循FIFO隊列的模式,而是不管隊列有沒有node,自己都可以去獲取鎖,不需要排隊
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
- unlock釋放鎖:其實就是把state從n(可能發生了鎖的重入,需要多次釋放)變成0,這個不區分公平與非公平,首先其實也是調用AQS的release方法,然後AQS在調用子類Sync的tryRelease方法。
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
protected final boolean tryRelease(int releases) {
// 獲取鎖的狀態
int c = getState() - releases;
// 獲得鎖的線程才能釋放鎖
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 直到鎖的狀態是0,說明鎖釋放成功,因爲有重入鎖
// 說明我們在一個線程裏面調用幾次lock,就要調用幾次unlock,才能最終釋放鎖
if (c == 0) {
free = true;
// 釋放線程的擁有者
setExclusiveOwnerThread(null);
}
// 設置鎖的狀態
setState(c);
return free;
}
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
– 以上爲《JAVA多線程(十五)Java多線程之ReentrantLock重入鎖》,如有不當之處請指出,我後續逐步完善更正,大家共同提高。謝謝大家對我的關注。
——厚積薄發(yuanxw)