synchronized詳解
- 公平鎖是指當(dāng)鎖可用時(shí),在鎖上等待時(shí)間最長(zhǎng)的線程將獲得鎖的使用權(quán)。
- 非公平鎖則隨機(jī)分配這種使用權(quán)种呐。
synchronized的三種使用方式
- 修飾實(shí)例方法(所謂實(shí)例對(duì)象鎖就是用synchronized修飾實(shí)例對(duì)象的實(shí)例方法,注意是實(shí)例方法彰亥,不是靜態(tài)方法)
- 修飾靜態(tài)方法(當(dāng)synchronized作用于靜態(tài)方法時(shí),鎖的對(duì)象就是當(dāng)前類的Class對(duì)象)
- 修飾代碼塊(手動(dòng)指定鎖定對(duì)象,也可是是this,也可以是自定義的鎖)
// 修飾實(shí)例方法
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
public synchronized void method() {
System.out.println("我是線程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "結(jié)束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}輸出結(jié)果:
我是線程Thread-0 Thread-0結(jié)束 我是線程Thread-1 Thread-1結(jié)束
// 修飾靜態(tài)方法
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
// synchronized用在普通方法上杏头,默認(rèn)的鎖就是this,當(dāng)前實(shí)例
public synchronized void method() {
System.out.println("我是線程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "結(jié)束");
}
public static void main(String[] args) {
// t1和t2對(duì)應(yīng)的this是兩個(gè)不同的實(shí)例沸呐,所以代碼不會(huì)串行
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}輸出結(jié)果:
我是線程Thread-0 我是線程Thread-1 Thread-1結(jié)束 Thread-0結(jié)束
//修飾代碼塊
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
@Override
public void run() {
// 同步代碼塊形式——鎖為this,兩個(gè)線程使用的鎖是一樣的,線程1必須要等到線程0釋放了該鎖后醇王,才能執(zhí)行
synchronized (this) {
System.out.println("我是線程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "結(jié)束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}輸出結(jié)果: 我是線程Thread-0 Thread-0結(jié)束 我是線程Thread-1 Thread-1結(jié)束
synchronized的性質(zhì)
1.可重入性
概念:指同一個(gè)線程外層函數(shù)獲取到鎖之后,內(nèi)層函數(shù)可以直接使用該鎖
好處:避免死鎖崭添,提升封裝性(如果不可重入寓娩,假設(shè)method1拿到鎖之后,在method1中又調(diào)用了method2,如果method2沒辦法使用method1拿到的鎖,那method2將一直等待棘伴,但是method1由于未執(zhí)行完畢寞埠,又無法釋放鎖,就導(dǎo)致了死鎖排嫌,可重入正好避免這這種情況)但是synchronized的使用也有可能會(huì)出現(xiàn)死鎖,這里的避免死鎖的更底層的概念缰犁。
2.不可中斷性
概念:如果這個(gè)鎖被B線程獲取淳地,如果A線程想要獲取這把鎖,只能選擇等待或者阻塞帅容,直到B線程釋放這把鎖颇象,如果B線程一直不釋放這把鎖,那么A線程將一直等待并徘。
相比之下遣钳,未來的Lock類,可以擁有中斷的能力(如果一個(gè)線程等待鎖的時(shí)間太長(zhǎng)了麦乞,有權(quán)利中斷當(dāng)前已經(jīng)獲取鎖的線程的執(zhí)行蕴茴,也可以退出等待)
synchronized的原理
public class SynchronizedDemo2 {
Object object = new Object();
public void method1() {
synchronized (object) {
}
}
}
使用javac命令進(jìn)行編譯生成.class文件
>javac SynchronizedDemo2.java
使用javap命令反編譯查看.class文件的信息
>javap -verbose SynchronizedDemo2.class
得到如下的信息:
關(guān)注紅色方框里的monitorenter和monitorexit即可。
Monitorenter和Monitorexit指令姐直,會(huì)讓對(duì)象在執(zhí)行倦淀,使其鎖計(jì)數(shù)器加1或者減1。每一個(gè)對(duì)象在同一時(shí)間只與一個(gè)monitor(鎖)相關(guān)聯(lián)声畏,而一個(gè)monitor在同一時(shí)間只能被一個(gè)線程獲得撞叽,一個(gè)對(duì)象在嘗試獲得與這個(gè)對(duì)象相關(guān)聯(lián)的Monitor鎖的所有權(quán)的時(shí)候,monitorenter指令會(huì)發(fā)生如下3中情況之一:
1)monitor計(jì)數(shù)器為0插龄,意味著目前還沒有被獲得愿棋,那這個(gè)線程就會(huì)立刻獲得然后把鎖計(jì)數(shù)器+1,一旦+1均牢,別的線程再想獲取糠雨,就需要等待
2)如果這個(gè)monitor已經(jīng)拿到了這個(gè)鎖的所有權(quán),又重入了這把鎖徘跪,那鎖計(jì)數(shù)器就會(huì)累加见秤,變成2,并且隨著重入的次數(shù)真椿,會(huì)一直累加
3)這把鎖已經(jīng)被別的線程獲取了鹃答,等待鎖釋放
monitorexit指令:釋放對(duì)于monitor的所有權(quán),釋放過程很簡(jiǎn)單突硝,就是講monitor的計(jì)數(shù)器減1测摔,如果減完以后,計(jì)數(shù)器不是0,則代表剛才是重入進(jìn)來的锋八,當(dāng)前線程還繼續(xù)持有這把鎖的所有權(quán)浙于,如果計(jì)數(shù)器變成0,則代表當(dāng)前線程不再擁有該monitor的所有權(quán)挟纱,即釋放鎖羞酗。
2.可重入原理:加鎖次數(shù)計(jì)數(shù)器
jvm會(huì)負(fù)責(zé)跟蹤對(duì)象被加鎖的次數(shù)
線程第一次獲得所,計(jì)數(shù)器+1紊服,當(dāng)鎖重入的時(shí)候檀轨,計(jì)數(shù)器會(huì)遞增
當(dāng)任務(wù)離開的時(shí)候(一個(gè)同步代碼塊的代碼執(zhí)行結(jié)束),計(jì)數(shù)器會(huì)減1欺嗤,當(dāng)減為0的時(shí)候参萄,鎖被完全釋放。
synchronized的缺陷
效率低:鎖的釋放情況少煎饼,只有代碼執(zhí)行完畢或者異常結(jié)束才會(huì)釋放鎖讹挎;試圖獲取鎖的時(shí)候不能設(shè)定超時(shí),不能中斷一個(gè)正在使用鎖的線程吆玖,相對(duì)而言筒溃,Lock可以中斷和設(shè)置超時(shí)
不夠靈活:加鎖和釋放的時(shí)機(jī)單一,每個(gè)鎖僅有一個(gè)單一的條件(某個(gè)對(duì)象)沾乘,相對(duì)而言铡羡,讀寫鎖更加靈活
無法知道是否成功獲得鎖,相對(duì)而言意鲸,Lock可以拿到狀態(tài)烦周,如果成功獲取鎖,....怎顾,如果獲取失敗,.....
3.可見性原理:
- 在加鎖前會(huì)將工作內(nèi)存的值全部重新加載一遍读慎,保證最新;釋放鎖前將工作內(nèi)存的值全部更新到主存槐雾;由于在帶鎖期間夭委,沒有其他線程能訪問本線程正在使用的共享變量,這樣就保證了可見性
- 由于Synchronized修飾的代碼塊都是原子性執(zhí)行的募强,即一旦開始做株灸,就會(huì)一直執(zhí)行完畢,期間有其他線程不可以訪問本線程所使用的共享變量擎值,這樣慌烧,即便指令重排了也不會(huì)出現(xiàn)問題。
4.synchronized非公平鎖原理:synchronized底層使用操作系統(tǒng)mutext鎖實(shí)現(xiàn)的鸠儿,而mutext鎖并不保證公平性屹蚊,這樣做的目的就是為了提高執(zhí)行性能厕氨,當(dāng)然缺點(diǎn)是有可能會(huì)產(chǎn)生線程饑餓。
synchronized和@Transactional一起使用時(shí)出現(xiàn)了synchronized失效的原因
1.@Transactional 注解是Spring 提供來進(jìn)行控制事務(wù)的注解汹粤,當(dāng)注解標(biāo)明在方法上時(shí)命斧, 則會(huì)對(duì) 該方法進(jìn)行做AOP 增強(qiáng)(動(dòng)態(tài)代理),然后在方法執(zhí)行前嘱兼,開啟事務(wù)国葬,執(zhí)行后提交事務(wù)。
2.是因?yàn)镾ynchronized鎖定的是當(dāng)前調(diào)用方法對(duì)象,而Spring AOP 處理事務(wù)會(huì)進(jìn)行生成一個(gè)代理對(duì)象芹壕,并在代理對(duì)象執(zhí)行方法前的事務(wù)開啟汇四,方法執(zhí)行完的事務(wù)提交,所以說哪雕,事務(wù)的開啟和提交并不是在 Synchronized 鎖定的范圍內(nèi)船殉。出現(xiàn)同步鎖失效的原因是:當(dāng)A(線程) 執(zhí)行完insert()方法鲫趁,會(huì)進(jìn)行釋放同步鎖斯嚎,去做提交事務(wù),但在A(線程)還沒有提交完事務(wù)之前挨厚,B(線程)進(jìn)行執(zhí)行insert() 方法堡僻,執(zhí)行完畢之后和A(線程)一起提交事務(wù), 這時(shí)候就會(huì)出現(xiàn)線程安全問題,就會(huì)插入重復(fù)數(shù)據(jù)疫剃。
cglib代理synchronized的方法钉疫,同步關(guān)鍵字是否還在?
在動(dòng)態(tài)生成的子類中不存在synchronized巢价,但synchronized會(huì)有效果牲阁。因?yàn)閯?dòng)態(tài)代理回去調(diào)用父類的方法,父類的方法是被鎖住的壤躲。
Synchronized本質(zhì)上是通過什么保證線程安全的城菊?分三個(gè)方面回答:加鎖和釋放鎖的原理,可重入原理碉克,保證可見性原理凌唬。
Synchronized由什么樣的缺陷?Java Lock是怎么彌補(bǔ)這些缺陷的漏麦。
- 1.當(dāng)線程嘗試獲取鎖的時(shí)候客税,如果獲取不到鎖會(huì)一直阻塞,這個(gè)阻塞的過程撕贞,用戶無法控制
- 2.如果獲取鎖的線程進(jìn)入休眠或者阻塞更耻,除非當(dāng)前線程異常,否則其他線程嘗試獲取鎖必須一直等待
Synchronized和Lock的對(duì)比捏膨,和選擇酥夭?
Synchronized在使用時(shí)有何注意事項(xiàng)?
Synchronized修飾的方法在拋出異常時(shí),會(huì)釋放鎖嗎?
- 線程在執(zhí)行同步方法時(shí)拋出異常熬北,會(huì)自動(dòng)釋放鎖疙描,以便其他線程可以拿到鎖繼續(xù)執(zhí)行。
多個(gè)線程等待同一個(gè)synchronized鎖的時(shí)候讶隐,JVM如何選擇下一個(gè)獲取鎖的線程起胰?
- 這個(gè)問題就涉及到內(nèi)部鎖的調(diào)度機(jī)制,線程獲取 synchronized 對(duì)應(yīng)的鎖巫延,也是有具體的調(diào)度算法的效五,這個(gè)和具體的虛擬機(jī)版本和實(shí)現(xiàn)都有關(guān)系,所以下一個(gè)獲取鎖的線程是事先沒辦法預(yù)測(cè)的炉峰。
Synchronized使得同時(shí)只有一個(gè)線程可以執(zhí)行畏妖,性能比較差,有什么提升的方法疼阔?
- 優(yōu)化 synchronized 的使用范圍戒劫,讓臨界區(qū)的代碼在符合要求的情況下盡可能的小。
- 使用其他類型的 lock(鎖)婆廊,synchronized 使用的鎖經(jīng)過 jdk 版本的升級(jí)迅细,性能已經(jīng)大幅提升了,但相對(duì)于更加輕量級(jí)的鎖(如讀寫鎖)還是偏重一點(diǎn)淘邻,所以可以選擇更合適的鎖茵典。
我想更加靈活地控制鎖的釋放和獲取(現(xiàn)在釋放鎖和獲取鎖的時(shí)機(jī)都被規(guī)定死了),怎么辦宾舅?
- 可以根據(jù)需要實(shí)現(xiàn)一個(gè) Lock 接口统阿,這樣鎖的獲取和釋放就能完全被我們控制了。
什么是鎖的升級(jí)和降級(jí)筹我?什么是JVM里的偏斜鎖扶平、輕量級(jí)鎖、重量級(jí)鎖崎溃?
JDK6 之后蜻直,不斷優(yōu)化 synchronized,提供了三種鎖的實(shí)現(xiàn)袁串,分別是偏向鎖概而、輕量級(jí)鎖、重量級(jí)鎖囱修,還提供自動(dòng)的升級(jí)和降級(jí)機(jī)制赎瑰。對(duì)于不同的競(jìng)爭(zhēng)情況,會(huì)自動(dòng)切換到合適的鎖實(shí)現(xiàn)破镰。當(dāng)沒有競(jìng)爭(zhēng)出現(xiàn)時(shí)餐曼,默認(rèn)使用偏斜鎖压储,也即是在對(duì)象頭的 Mark Word 部分設(shè)置線程ID,來表示鎖對(duì)象偏向的線程源譬,但這并不是互斥鎖集惋;當(dāng)有其他線程試圖鎖定某個(gè)已被偏斜過的鎖對(duì)象,JVM 就撤銷偏斜鎖踩娘,切換到輕量級(jí)鎖刮刑,輕量級(jí)鎖依賴 CAS 操作對(duì)象頭的 Mark Word 來試圖獲取鎖,如果重試成功养渴,就使用普通的輕量級(jí)鎖雷绢;否則進(jìn)一步升級(jí)為重量級(jí)鎖。鎖的降級(jí)發(fā)生在當(dāng) JVM 進(jìn)入安全點(diǎn)后理卑,檢查是否有閑置的鎖翘紊,并試圖進(jìn)行降級(jí)。鎖的升級(jí)和降級(jí)都是出于性能的考慮藐唠。
偏向鎖:在線程競(jìng)爭(zhēng)不激烈的情況下帆疟,減少加鎖和解鎖的性能損耗,在對(duì)象頭中保存獲得鎖的線程ID信息中捆,如果這個(gè)線程再次請(qǐng)求鎖鸯匹,就用對(duì)象頭中保存的ID和自身線程ID對(duì)比坊饶,如果相同泄伪,就說明這個(gè)線程獲取鎖成功,不用再進(jìn)行加解鎖操作了匿级,省去了再次同步判斷的步驟蟋滴,提升了性能。輕量級(jí)鎖:再線程競(jìng)爭(zhēng)比偏向鎖更激烈的情況下痘绎,在線程的棧內(nèi)存中分配一段空間作為鎖的記錄空間(輕量級(jí)鎖對(duì)應(yīng)的對(duì)象的對(duì)象頭字段的拷貝)津函,線程通過CAS競(jìng)爭(zhēng)輕量級(jí)鎖,試圖把對(duì)象的對(duì)象頭字段改成指向鎖記錄的空間孤页,如果成功就說明獲取輕量級(jí)鎖成功尔苦,如果失敗,則進(jìn)入自旋(一定次數(shù)的循環(huán)行施,避免線程直接進(jìn)入阻塞狀態(tài))試圖獲取鎖允坚,如果自旋到一定次數(shù)還不能獲取到鎖,則進(jìn)入重量級(jí)鎖蛾号。
自旋鎖:獲取輕量級(jí)鎖失敗后稠项,避免線程直接進(jìn)入阻塞狀態(tài)而采取的循環(huán)一定次數(shù)去嘗試獲取鎖。(線程進(jìn)入阻塞狀態(tài)和非阻塞狀態(tài)都是涉及到系統(tǒng)層面的鲜结,需要在用戶態(tài)到內(nèi)核態(tài)之間切換展运,非常消耗系統(tǒng)資源)實(shí)驗(yàn)證明活逆,鎖的持有時(shí)間一般是非常短的,所以一般多次嘗試就能競(jìng)爭(zhēng)到鎖拗胜。
重量級(jí)鎖:在 JVM 中又叫做對(duì)象監(jiān)視器(monitor)蔗候,鎖對(duì)象的對(duì)象頭字段指向的是一個(gè)互斥量,多個(gè)線程競(jìng)爭(zhēng)鎖埂软,競(jìng)爭(zhēng)失敗的線程進(jìn)入阻塞狀態(tài)(操作系統(tǒng)層面)琴庵,并在鎖對(duì)象的一個(gè)等待池中等待被喚醒,被喚醒后的線程再次競(jìng)爭(zhēng)鎖資源仰美。
ReentrantLock詳解
- 1.可重入鎖:可重入鎖是指同一個(gè)線程可以多次獲得同一把鎖迷殿;ReentrantLock和關(guān)鍵字Synchronized都是可重入鎖
- 2.可中斷鎖:可中斷鎖時(shí)子線程在獲取鎖的過程中,是否可以相應(yīng)線程中斷操作咖杂。synchronized是不可中斷的庆寺,ReentrantLock是可中斷的
- 3.公平鎖和非公平鎖:公平鎖是指多個(gè)線程嘗試獲取同一把鎖的時(shí)候,獲取鎖的順序按照線程到達(dá)的先后順序獲取诉字,而不是隨機(jī)插隊(duì)的方式獲取懦尝。synchronized是非公平鎖,而ReentrantLock是兩種都可以實(shí)現(xiàn)壤圃,不過默認(rèn)是非公平鎖
ReentrantLock的基本使用
public class Demo3 {
private static int num = 0;
private static ReentrantLock lock = new ReentrantLock();
private static void add() {
lock.lock();
try {
num++;
} finally {
//注意lock.unlock()一定要放在finally中陵霉,否則,
//若程序出現(xiàn)了異常伍绳,鎖沒有釋放踊挠,那么其他線程就再也沒有機(jī)會(huì)獲取這個(gè)鎖了。
lock.unlock();
}
}
public static class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Demo3.add();
}
}
}
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
T t2 = new T();
T t3 = new T();
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(Demo3.num);
}
}
ReentrantLock的使用過程:
1.創(chuàng)建鎖:ReentrantLock lock = new ReentrantLock();
2.獲取鎖:lock.lock()
3.釋放鎖:lock.unlock();
ReentrantLock的方法
無參構(gòu)造器(默認(rèn)為非公平鎖)
public ReentrantLock() {
sync = new NonfairSync();//默認(rèn)是非公平的
}
帶布爾值的構(gòu)造器(是否公平)
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();//fair為true冲杀,公平鎖效床;反之,非公平鎖
}
Sync的lock方法是抽象的权谁,實(shí)際的lock會(huì)代理到FairSync或是NonFairSync上(根據(jù)用戶的選擇來決定剩檀,公平鎖還是非公平鎖)
public void lock() {
sync.lock();//代理到Sync的lock方法上
}
此方法響應(yīng)中斷,當(dāng)線程在阻塞中的時(shí)候旺芽,若被中斷沪猴,會(huì)拋出InterruptedException異常
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);//代理到sync的相應(yīng)方法上,同lock方法的區(qū)別是此方法響應(yīng)中斷
}
tryLock采章,嘗試獲取鎖运嗜,成功則直接返回true,不成功也不耽擱時(shí)間共缕,立即返回false洗出。
public boolean tryLock() {
return sync.nonfairTryAcquire(1);//代理到sync的相應(yīng)方法上
}
ReentrantLock的實(shí)現(xiàn)原理:
- 它的設(shè)計(jì)基于AQS框架
- 在 Lock 中,用到了一個(gè)同步隊(duì)列 AQS图谷,全稱 AbstractQueuedSynchronizer翩活,它 是一個(gè)同步工具也是Lock用來實(shí)現(xiàn)線程同步的核心組件阱洪。如果你搞懂了AQS,那 么J.U.C中絕大部分的工具都能輕松掌握
AQS 的兩種功能
- 從使用層面來說菠镇,AQS的功能分為兩種:獨(dú)占和共享
- 獨(dú)占鎖(寫鎖)冗荸,每次只能有一個(gè)線程持有鎖,比如前面給大家演示的ReentrantLock就是 以獨(dú)占方式實(shí)現(xiàn)的互斥鎖
- 共 享 鎖(讀鎖) 利耍, 允 許 多 個(gè) 線 程 同 時(shí) 獲 取 鎖 蚌本, 并 發(fā) 訪 問 共 享 資 源 , 比 如 ReentrantReadWriteLock
AQS 的內(nèi)部實(shí)現(xiàn)
- AQS 隊(duì)列內(nèi)部維護(hù)的是一個(gè) FIFO (先進(jìn)先出)的雙向鏈表隘梨,這種結(jié)構(gòu)的特點(diǎn)是每個(gè)數(shù)據(jù)結(jié)構(gòu)都有兩個(gè)指針程癌,分別指向直接的后繼節(jié)點(diǎn)和直接前驅(qū)節(jié)點(diǎn)。
-
所以雙向鏈表可以從任 意一個(gè)節(jié)點(diǎn)開始很方便的訪問前驅(qū)和后繼轴猎。每個(gè) Node 其實(shí)是由線程封裝嵌莉,當(dāng)線 程爭(zhēng)搶鎖失敗后會(huì)封裝成Node加入到ASQ隊(duì)列中去;當(dāng)獲取鎖的線程釋放鎖以 后捻脖,會(huì)從隊(duì)列中喚醒一個(gè)阻塞的節(jié)點(diǎn)(線程)锐峭。
image.png
從源碼看ReentrantLock非公平鎖實(shí)現(xiàn)原理
簡(jiǎn)單總結(jié)下流程:
1.先獲取state值,若為0可婶,意味著此時(shí)沒有線程獲取到資源沿癞,CAS將其設(shè)置為1,設(shè)置成功則代表獲取到排他鎖了矛渴;
2.若state大于0椎扬,肯定有線程已經(jīng)搶占到資源了,此時(shí)再去判斷是否就是自己搶占的曙旭,是的話盗舰,state累加晶府,返回true桂躏,重入成功,state的值即是線程重入的次數(shù)川陆;
3.其他情況剂习,則獲取鎖失敗。
NonFairSync(非公平可重入鎖)
static final class NonfairSync extends Sync {//繼承Sync
private static final long serialVersionUID = 7316153563782823691L;
/** 獲取鎖 */
final void lock() {
if (compareAndSetState(0, 1))//CAS設(shè)置state狀態(tài)较沪,若原值是0鳞绕,將其置為1
setExclusiveOwnerThread(Thread.currentThread());//將當(dāng)前線程標(biāo)記為已持有鎖
else
acquire(1);//若設(shè)置失敗,調(diào)用AQS的acquire方法尸曼,acquire又會(huì)調(diào)用我們下面重寫的tryAcquire方法们何。這里說的調(diào)用失敗有兩種情況:1當(dāng)前沒有線程獲取到資源,state為0控轿,但是將state由0設(shè)置為1的時(shí)候冤竹,其他線程搶占資源拂封,將state修改了,導(dǎo)致了CAS失旔腥洹冒签;2 state原本就不為0,也就是已經(jīng)有線程獲取到資源了钟病,有可能是別的線程獲取到資源萧恕,也有可能是當(dāng)前線程獲取的,這時(shí)線程又重復(fù)去獲取肠阱,所以去tryAcquire中的nonfairTryAcquire我們應(yīng)該就能看到可重入的實(shí)現(xiàn)邏輯了票唆。
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);//調(diào)用Sync中的方法
}
}
nonfairTryAcquire()
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();//獲取當(dāng)前線程
int c = getState();//獲取當(dāng)前state值
if (c == 0) {//若state為0,意味著沒有線程獲取到資源屹徘,CAS將state設(shè)置為1惰说,并將當(dāng)前線程標(biāo)記我獲取到排他鎖的線程,返回true
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//若state不為0缘回,但是持有鎖的線程是當(dāng)前線程
int nextc = c + acquires;//state累加1
if (nextc < 0) // int類型溢出了
throw new Error("Maximum lock count exceeded");
setState(nextc);//設(shè)置state吆视,此時(shí)state大于1,代表著一個(gè)線程多次獲鎖酥宴,state的值即是線程重入的次數(shù)
return true;//返回true啦吧,獲取鎖成功
}
return false;//獲取鎖失敗了
}
從源碼看ReentrantLock公平鎖實(shí)現(xiàn)原理
FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);//直接調(diào)用AQS的模板方法acquire,acquire會(huì)調(diào)用下面我們重寫的這個(gè)tryAcquire
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();//獲取當(dāng)前線程
int c = getState();//獲取state值
if (c == 0) {//若state為0拙寡,意味著當(dāng)前沒有線程獲取到資源授滓,那就可以直接獲取資源了嗎?NO!這不就跟之前的非公平鎖的邏輯一樣了嘛肆糕“愣眩看下面的邏輯
if (!hasQueuedPredecessors() &&//判斷在時(shí)間順序上,是否有申請(qǐng)鎖排在自己之前的線程诚啃,若沒有淮摔,才能去獲取,CAS設(shè)置state始赎,并標(biāo)記當(dāng)前線程為持有排他鎖的線程和橙;反之,不能獲仍於狻魔招!這即是公平的處理方式。
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;
}
}
可以看到,公平鎖的大致邏輯與非公平鎖是一致的杆逗,不同的地方在于有了!hasQueuedPredecessors()這個(gè)判斷邏輯乡翅,即便state為0吁讨,也不能貿(mào)然直接去獲取,要先去看有沒有還在排隊(duì)的線程峦朗,若沒有建丧,才能嘗試去獲取,做后面的處理波势。反之翎朱,返回false,獲取失敗尺铣。
看看這個(gè)判斷是否有排隊(duì)中線程的邏輯
hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
Node t = tail; // 尾結(jié)點(diǎn)
Node h = head;//頭結(jié)點(diǎn)
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());//判斷是否有排在自己之前的線程
}
注意
- 1.ReentrantLock可以實(shí)現(xiàn)公平鎖和非公平鎖
- 2.ReentrantLock默認(rèn)實(shí)現(xiàn)的是非公平鎖
- 3.ReentrantLock的獲取鎖和釋放鎖必須成對(duì)出現(xiàn)拴曲,鎖了幾次,也要釋放幾次
- 4.釋放鎖的操作必須放在finally中執(zhí)行
- 5.lockInterruptibly()實(shí)例方法可以相應(yīng)線程的中斷方法凛忿,調(diào)用線程的- interrupt()方法時(shí)澈灼,lockInterruptibly()方法會(huì)觸發(fā)InterruptedException異常
- 6.關(guān)于InterruptedException異常說一下,看到方法聲明上帶有 throws InterruptedException店溢,表示該方法可以相應(yīng)線程中斷叁熔,調(diào)用線程的interrupt()方法時(shí),這些方法會(huì)觸發(fā)InterruptedException異常床牧,觸發(fā)InterruptedException時(shí)荣回,線程的中斷中斷狀態(tài)會(huì)被清除。所以如果程序由于調(diào)用interrupt()方法而觸發(fā)InterruptedException異常戈咳,線程的標(biāo)志由默認(rèn)的false變?yōu)閠ure心软,然后又變?yōu)閒alse
- 7.實(shí)例方法tryLock()會(huì)嘗試獲取鎖,會(huì)立即返回著蛙,返回值表示是否獲取成功
- 8.實(shí)例方法tryLock(long timeout, TimeUnit unit)會(huì)在指定的時(shí)間內(nèi)嘗試獲取鎖删铃,指定的時(shí)間內(nèi)是否能夠獲取鎖,都會(huì)返回踏堡,返回值表示是否獲取鎖成功猎唁,該方法會(huì)響應(yīng)線程的中斷