前言
????面試題:synchronized是可重入鎖嗎垮抗?
????答案:synchronized是可重入鎖。ReentrantLock也是的拗引。
1借宵、什么是可重入鎖呢?
????關(guān)于什么是可重入鎖矾削,我們先來(lái)看一段維基百科的定義壤玫。
若一個(gè)程序或子程序可以“在任意時(shí)刻被中斷然后操作系統(tǒng)調(diào)度執(zhí)行另外一段代碼豁护,這段代碼又調(diào)用了該子程序不會(huì)出錯(cuò)”,則稱(chēng)其為可重入(reentrant或re-entrant)的欲间。即當(dāng)該子程序正在運(yùn)行時(shí)楚里,執(zhí)行線程可以再次進(jìn)入并執(zhí)行它,仍然獲得符合設(shè)計(jì)時(shí)預(yù)期的結(jié)果猎贴。與多線程并發(fā)執(zhí)行的線程安全不同班缎,可重入強(qiáng)調(diào)對(duì)單個(gè)線程執(zhí)行時(shí)重新進(jìn)入同一個(gè)子程序仍然是安全的。
????通俗來(lái)說(shuō):當(dāng)線程請(qǐng)求一個(gè)由其它線程持有的對(duì)象鎖時(shí)她渴,該線程會(huì)阻塞达址,而當(dāng)線程請(qǐng)求由自己持有的對(duì)象鎖時(shí),如果該鎖是重入鎖趁耗,請(qǐng)求就會(huì)成功沉唠,否則阻塞。
????再換句話說(shuō):可重入就是說(shuō)某個(gè)線程已經(jīng)獲得某個(gè)鎖苛败,可以再次獲取鎖而不會(huì)出現(xiàn)死鎖满葛。
2、自己寫(xiě)代碼驗(yàn)證下可重入和不可重入
????我們啟動(dòng)一個(gè)線程t1罢屈,調(diào)用addOne()方法來(lái)執(zhí)行加1操作嘀韧。在addOne方法里面t1會(huì)獲得rtl鎖,然后調(diào)用get()方法缠捌,在get()方法里再次請(qǐng)求獲取trl鎖锄贷。
????因?yàn)樽罱K能打印value=1,說(shuō)明t1在第二次獲取鎖的時(shí)候并沒(méi)有阻塞鄙币。說(shuō)明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();
// 新建一個(gè)線程 進(jìn)行加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();
// 新建一個(gè)線程 進(jìn)行加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、自己如何實(shí)現(xiàn)一個(gè)可重入和不可重入鎖呢
不可重入:
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();
}
}
}
}
????從代碼實(shí)現(xiàn)來(lái)看绩衷,可重入鎖增加了兩個(gè)狀態(tài)蹦魔,鎖的計(jì)數(shù)器和被鎖的線程,實(shí)現(xiàn)基本上和不可重入的實(shí)現(xiàn)一樣咳燕,如果不同的線程進(jìn)來(lái)勿决,這個(gè)鎖是沒(méi)有問(wèn)題的,但是如果進(jìn)行遞歸計(jì)算的時(shí)候招盲,如果加鎖低缩,不可重入鎖就會(huì)出現(xiàn)死鎖的問(wèn)題。
4、ReentrantLock如何實(shí)現(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();
}
看下非公平鎖,它是繼承抽象類(lèi)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);
}
}
看下公平鎖玩般,它也是繼承抽象類(lèi)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 和抽象類(lèi)Sync 都是ReentrantLock的內(nèi)部類(lèi)。
????Sync的定義坏为,它是繼承AbstractQueuedSynchronizer的究驴,AbstractQueuedSynchronizer既是我們常說(shuō)的AQS(后面我也會(huì)整理一篇)
abstract static class Sync extends AbstractQueuedSynchronizer {
}
????好了,繼承關(guān)系清楚了 匀伏,現(xiàn)在我們看下ReentrantLock是如何實(shí)現(xiàn)可重入的
????我們?cè)赼ddOne()和get()兩個(gè)方法加鎖的地方都打上斷點(diǎn)洒忧。然后開(kāi)始調(diào)式:
- addOne方法獲取鎖的時(shí)候走到NonfairSync的“compareAndSetState(0, 1)”,通過(guò)CAS設(shè)置state的值為1帘撰,調(diào)用成功跑慕,并設(shè)置當(dāng)前鎖被持有的線程為當(dāng)前線程t1;
- 繼續(xù)調(diào)試,get方法獲取鎖的時(shí)候走到NonfairSync的“compareAndSetState(0, 1)”摧找,通過(guò)CAS設(shè)置state的值為1,調(diào)用失敗(因?yàn)橐呀?jīng)被當(dāng)前線程t1鎖占有)牢硅,走到else里面蹬耘,繼續(xù)往里看;
- 走到NonfairSync的tryAcquire方法,再往里走;
- 會(huì)調(diào)用Sync抽象類(lèi)里面的nonfairTryAcquire方法减余。源碼解釋我都寫(xiě)在下面了综苔。
final boolean nonfairTryAcquire(int acquires) {
// 當(dāng)前線程
final Thread current = Thread.currentThread();
// state變量的值
int c = getState();
// 因?yàn)閏當(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;
}
????到此如筛,可重入鎖加鎖的過(guò)程分析完畢。解鎖的過(guò)程一樣抒抬,希望你能自己debug下【調(diào)用的是Sync抽象類(lèi)里面的tryRelease方法】
????我這里總結(jié)一下:
當(dāng)線程嘗試獲取鎖時(shí)杨刨,可重入鎖先嘗試獲取并更新state值
如果state == 0表示沒(méi)有其他線程在執(zhí)行同步代碼,則通過(guò)CAS把state置為1 會(huì)成功擦剑,當(dāng)前線程繼續(xù)執(zhí)行妖胀。
如果status != 0,通過(guò)CAS把state置為1 會(huì)失敗惠勒,然后判斷當(dāng)前線程是否是獲取到這個(gè)鎖的線程赚抡,如果是的話執(zhí)行state+1,且當(dāng)前線程可以再次獲取鎖纠屋。釋放鎖時(shí)涂臣,可重入鎖同樣先獲取當(dāng)前state的值,在當(dāng)前線程是持有鎖的線程的前提下售担。
如果status-1 == 0赁遗,則表示當(dāng)前線程所有重復(fù)獲取鎖的操作都已經(jīng)執(zhí)行完畢署辉,然后該線程才會(huì)真正釋放鎖。
????你需要注意的是state變量的定義吼和,其實(shí)AQS的實(shí)現(xiàn)類(lèi)都是通過(guò)控制state的值來(lái)控制鎖的狀態(tài)的涨薪。它被volatile所修飾,能保證可見(jiàn)性炫乓。
private volatile int state;
????擴(kuò)展:如果要通過(guò)AQS的state來(lái)實(shí)現(xiàn)非可重入鎖怎么實(shí)現(xiàn)呢刚夺?明確這兩點(diǎn)就可以了:
- 獲取鎖時(shí):去獲取并嘗試更新當(dāng)前status的值,如果status != 0的話會(huì)導(dǎo)致其獲取鎖失敗末捣,當(dāng)前線程阻塞侠姑。
- 釋放鎖時(shí):在確定當(dāng)前線程是持有鎖的線程之后,直接將status置為0箩做,將鎖釋放莽红。
5、可重入鎖的特點(diǎn)
????可重入鎖的一個(gè)優(yōu)點(diǎn)是可一定程度避免死鎖邦邦。
????可重入鎖能避免一定線程的等待安吁,可想而知可重入鎖性能會(huì)高于非可重入鎖。你可以寫(xiě)程序測(cè)試一下哦H枷健9淼辍!
推薦閱讀:
Java內(nèi)存模型-volatile的應(yīng)用(實(shí)例講解)
synchronized解決原子性-synchronized的三種應(yīng)用方式(實(shí)例講解)
線程池-一文弄懂Java里面的線程池ThreadPoolExecutor
可重入鎖-面試題:synchronized是可重入鎖嗎
大徹大悟synchronized原理黔龟,鎖的升級(jí)