可重入鎖和非可重入鎖
廣義上的可重入鎖指的是可重複可遞迴呼叫的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個物件或者class),這樣的鎖就叫做可重入鎖。
可重入鎖:
ReentrantLock和synchronized都是可重入鎖,下面是一個用synchronized實現的例子:
public class ReentrantTest implements Runnable {
public synchronized void get() {
System.out.println(Thread.currentThread().getName());
set();
}public synchronized void set() {
System.out.println(Thread.currentThread().getName());
}public void run() {
get();
}public static void main(String[] args) {
ReentrantTest rt = new ReentrantTest();
for(;;){
new Thread(rt).start();
}
}
}
整個過程沒有發生死鎖的情況,擷取一部分輸出結果如下:
Thread-8492
Thread-8492
Thread-8494
Thread-8494
Thread-8495
Thread-8495
Thread-8493
Thread-8493
set()和get()同時輸出了執行緒名稱,表明即使遞迴使用synchronized也沒有發生死鎖,證明其是可重入的。
不可重入鎖
不可重入鎖,與可重入鎖相反,不可遞迴呼叫,遞迴呼叫就發生死鎖。
自定義模擬不可重入鎖:
一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,程式碼如下:
package com.thread;
import java.util.concurrent.atomic.AtomicReference;
public class UnreentrantLock {
private AtomicReference<Thread> owner = new AtomicReference<Thread>();//記錄當前鎖的持有執行緒物件
public void lock() {//加鎖
Thread current = Thread.currentThread();//獲取當前執行緒物件
for (; ; ) {//自旋(被當前執行緒或其他執行緒持有鎖,就會迴圈)
if (owner.compareAndSet(null, current)) {//只有鎖可用即為null,才能設定當前執行緒為鎖持有物件,並返回true
return;
}
}
}
public void unlock() {//解鎖
Thread current = Thread.currentThread();//獲取當前執行緒物件
owner.compareAndSet(current, null);//設定鎖的持有物件為null
}
}
使用原子引用來存放執行緒,同一執行緒兩次呼叫lock()方法,如果不執行unlock()釋放鎖的話,第二次呼叫自旋的時候就會產生死鎖,這個鎖就不是可重入的。
自定義模擬可重入鎖:
實際上同一個執行緒不必每次都去釋放鎖再來獲取鎖,這樣的排程切換是很耗資源的。稍微改一下,把它變成一個可重入鎖:
package com.thread;
import java.util.concurrent.atomic.AtomicReference;
public class UnreentrantLock {
private AtomicReference<Thread> owner = new AtomicReference<Thread>();//記錄當前鎖的持有執行緒物件
private int state = 0;//記錄重入次數
public void lock() {//加鎖
Thread current = Thread.currentThread();//獲取當前執行緒物件
if (owner.compareAndSet(null, current)) {//當前鎖可用
state = 1;//狀態置為1
return;
} else {
if (current == owner.get()) {//如果當前執行緒持有鎖
state++;//重入次數加1
return;
}
for (; ; ) {//被其他執行緒持有就會繼續迴圈
if (owner.compareAndSet(null, current)) {//只有鎖可用即為null,才能設定當前執行緒為鎖持有物件,並返回true
return;
}
}
}
}
public void unlock() {//解鎖
Thread current = Thread.currentThread();//獲取當前執行緒物件
if (current == owner.get()) {//如果當前執行緒持有鎖
if (state > 0) {//重入次數大於0
state--;//重入次數減1
} else {
owner.compareAndSet(current, null);//設定鎖的持有物件為null
}
}
}
}
在執行每次操作之前,判斷當前鎖持有者是否是當前物件,採用state計數,不用每次去釋放鎖。
ReentrantLock中非公平的可重入鎖實現:
// 非公平方式獲取鎖,用於tryLock()方法
final boolean nonfairTryAcquire(int acquires) {
//當前執行緒
final Thread current = Thread.currentThread();
// 繼承至AbstractQueuedSynchronizer的方法
int c = getState();//獲取鎖狀態值
//沒有執行緒正在競爭該鎖
if (c == 0) {
// 繼承至AbstractQueuedSynchronizer的方法
if (compareAndSetState(0, acquires)) {//若state為0則將state修改為acquires的值,狀態0表示鎖沒有被佔用
setExclusiveOwnerThread(current);// 設定當前執行緒獨佔
return true;// 成功
}
} else if (current == getExclusiveOwnerThread()) {// 當前執行緒擁有該鎖
int nextc = c + acquires;// 增加重入次數
if (nextc < 0) // overflow(計數值小於0,則丟擲異常)
throw new Error("Maximum lock count exceeded");
// 繼承至AbstractQueuedSynchronizer的方法
setState(nextc);//設定鎖狀態值
return true;// 成功
}
return false;// 失敗
}