前言
????面試題:synchronized是可重入鎖嗎?
????答案:synchronized是可重入鎖。ReentrantLock也是的。
1、什么是可重入鎖呢?
????關(guān)于什么是可重入鎖,我們先來看一段維基百科的定義。
若一個程序或子程序可以“在任意時刻被中斷然后操作系統(tǒng)調(diào)度執(zhí)行另外一段代碼,這段代碼又調(diào)用了該子程序不會出錯”,則稱其為可重入(reentrant或re-entrant)的。即當(dāng)該子程序正在運行時,執(zhí)行線程可以再次進入并執(zhí)行它,仍然獲得符合設(shè)計時預(yù)期的結(jié)果。與多線程并發(fā)執(zhí)行的線程安全不同,可重入強調(diào)對單個線程執(zhí)行時重新進入同一個子程序仍然是安全的。
????通俗來說:當(dāng)線程請求一個由其它線程持有的對象鎖時,該線程會阻塞,而當(dāng)線程請求由自己持有的對象鎖時,如果該鎖是重入鎖,請求就會成功,否則阻塞。
????再換句話說:可重入就是說某個線程已經(jīng)獲得某個鎖,可以再次獲取鎖而不會出現(xiàn)死鎖。
2、自己寫代碼驗證下可重入和不可重入
????我們啟動一個線程t1,調(diào)用addOne()方法來執(zhí)行加1操作。在addOne方法里面t1會獲得rtl鎖,然后調(diào)用get()方法,在get()方法里再次請求獲取trl鎖。
????因為最終能打印value=1,說明t1在第二次獲取鎖的時候并沒有阻塞。說明ReentrantLock是可重入鎖。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantTest {
private final Lock rtl = new ReentrantLock();
int value = 0;
public static void main(String[] args) throws InterruptedException {
ReentrantTest test = new ReentrantTest();
// 新建一個線程 進行加1操作
Thread t1 = new Thread(() -> test.addOne());
t1.start();
// main線程等待t1線程執(zhí)行完
t1.join();
System.out.println(test.value);
}
public int get() {
// 獲取鎖
rtl.lock();
try {
return value;
} finally {
// 保證鎖能釋放
rtl.unlock();
}
}
public void addOne() {
// 獲取鎖
rtl.lock();
try {
value = 1 + get();
} finally {
// 保證鎖能釋放
rtl.unlock();
}
}
}
????換成synchronized的加鎖方式,同樣能打印value的值。證明synchronized也是可重入鎖。
public class ReentrantTest {
private final Object object = new Object();
int value = 0;
public static void main(String[] args) throws InterruptedException {
ReentrantTest test = new ReentrantTest();
// 新建一個線程 進行加1操作
Thread t1 = new Thread(() -> test.addOne());
t1.start();
t1.join();
System.out.println(test.value);
}
public int get() {
// 再此獲取鎖
synchronized (object) {
return value;
}
}
public void addOne() {
// 獲取鎖
synchronized (object) {
value = 1 + get();
}
}
}
3、自己如何實現(xiàn)一個可重入和不可重入鎖呢
不可重入:
public class Lock{
private boolean isLocked = false;
public synchronized void lock()
throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
可重入:
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock() throws InterruptedException{
Thread callingThread = Thread.currentThread();
while(isLocked && lockedBy != callingThread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
}
public synchronized void unlock(){
if(Thread.curentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
}
????從代碼實現(xiàn)來看,可重入鎖增加了兩個狀態(tài),鎖的計數(shù)器和被鎖的線程,實現(xiàn)基本上和不可重入的實現(xiàn)一樣,如果不同的線程進來,這個鎖是沒有問題的,但是如果進行遞歸計算的時候,如果加鎖,不可重入鎖就會出現(xiàn)死鎖的問題。
4、ReentrantLock如何實現(xiàn)可重入的
使用ReentrantLock你要知道:
ReentrantLock支持公平和非公平2種創(chuàng)建方式,默認(rèn)創(chuàng)建的是非公平模式的鎖。
看下它的構(gòu)造方法:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
看下非公平鎖,它是繼承抽象類Sync的:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
看下公平鎖,它也是繼承抽象類Sync的:
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;
}
}
????NonfairSync、FairSync 和抽象類Sync 都是ReentrantLock的內(nèi)部類。
????Sync的定義,它是繼承AbstractQueuedSynchronizer的,AbstractQueuedSynchronizer既是我們常說的AQS(后面我也會整理一篇)
abstract static class Sync extends AbstractQueuedSynchronizer {
}
????好了,繼承關(guān)系清楚了 ,現(xiàn)在我們看下ReentrantLock是如何實現(xiàn)可重入的
????我們在addOne()和get()兩個方法加鎖的地方都打上斷點。然后開始調(diào)式:
- addOne方法獲取鎖的時候走到NonfairSync的“compareAndSetState(0, 1)”,通過CAS設(shè)置state的值為1,調(diào)用成功,并設(shè)置當(dāng)前鎖被持有的線程為當(dāng)前線程t1;
- 繼續(xù)調(diào)試,get方法獲取鎖的時候走到NonfairSync的“compareAndSetState(0, 1)”,通過CAS設(shè)置state的值為1,調(diào)用失敗(因為已經(jīng)被當(dāng)前線程t1鎖占有),走到else里面,繼續(xù)往里看;
- 走到NonfairSync的tryAcquire方法,再往里走;
- 會調(diào)用Sync抽象類里面的nonfairTryAcquire方法。源碼解釋我都寫在下面了。
final boolean nonfairTryAcquire(int acquires) {
// 當(dāng)前線程
final Thread current = Thread.currentThread();
// state變量的值
int c = getState();
// 因為c當(dāng)前值為1,所以走else里面
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 判斷當(dāng)前線程 是不是 當(dāng)前鎖被持有的線程 ,判斷為 true
else if (current == getExclusiveOwnerThread()) {
// c + acquires = 1 + 1 = 2
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);// 將state的值賦值為2
return true;
}
return false;
}
????到此,可重入鎖加鎖的過程分析完畢。解鎖的過程一樣,希望你能自己debug下【調(diào)用的是Sync抽象類里面的tryRelease方法】
????我這里總結(jié)一下:
當(dāng)線程嘗試獲取鎖時,可重入鎖先嘗試獲取并更新state值
如果state == 0表示沒有其他線程在執(zhí)行同步代碼,則通過CAS把state置為1 會成功,當(dāng)前線程繼續(xù)執(zhí)行。
如果status != 0,通過CAS把state置為1 會失敗,然后判斷當(dāng)前線程是否是獲取到這個鎖的線程,如果是的話執(zhí)行state+1,且當(dāng)前線程可以再次獲取鎖。釋放鎖時,可重入鎖同樣先獲取當(dāng)前state的值,在當(dāng)前線程是持有鎖的線程的前提下。
如果status-1 == 0,則表示當(dāng)前線程所有重復(fù)獲取鎖的操作都已經(jīng)執(zhí)行完畢,然后該線程才會真正釋放鎖。
????你需要注意的是state變量的定義,其實AQS的實現(xiàn)類都是通過控制state的值來控制鎖的狀態(tài)的。它被volatile所修飾,能保證可見性。
private volatile int state;
????擴展:如果要通過AQS的state來實現(xiàn)非可重入鎖怎么實現(xiàn)呢?明確這兩點就可以了:
- 獲取鎖時:去獲取并嘗試更新當(dāng)前status的值,如果status != 0的話會導(dǎo)致其獲取鎖失敗,當(dāng)前線程阻塞。
- 釋放鎖時:在確定當(dāng)前線程是持有鎖的線程之后,直接將status置為0,將鎖釋放。
5、可重入鎖的特點
????可重入鎖的一個優(yōu)點是可一定程度避免死鎖。
????可重入鎖能避免一定線程的等待,可想而知可重入鎖性能會高于非可重入鎖。你可以寫程序測試一下哦!??!
推薦閱讀:
Java內(nèi)存模型-volatile的應(yīng)用(實例講解)
synchronized解決原子性-synchronized的三種應(yīng)用方式(實例講解)
線程池-一文弄懂Java里面的線程池ThreadPoolExecutor
可重入鎖-面試題:synchronized是可重入鎖嗎
大徹大悟synchronized原理,鎖的升級