Java?synchronized與死鎖深入探究
1.synchronized的特性
1). 互斥性
當(dāng)某個(gè)線程執(zhí)行到 synchronized 所修飾的對(duì)象時(shí) , 該線程對(duì)象會(huì)加鎖(lock) , 其他線程如果執(zhí)行到同一個(gè)對(duì)象的 synchronized 就會(huì)產(chǎn)生阻塞等待.
- 進(jìn)入 synchronized 修飾的代碼塊 , 相當(dāng)于加鎖.
- 退出 synchronized 修飾著代碼塊 , 相當(dāng)于解鎖.
synchronized 使用的鎖存儲(chǔ)在Java對(duì)象里 , 可以理解為每個(gè)對(duì)象在內(nèi)存中存儲(chǔ)時(shí) , 都有一塊內(nèi)存表示當(dāng)前的鎖定狀態(tài).類似于公廁的"有人" , "無(wú)人".
如果是"無(wú)人"狀態(tài) , 此時(shí)就可以使用 , 使用時(shí)需設(shè)置為"有人"狀態(tài).
如果是"有人"狀態(tài) , 此時(shí)就需要排隊(duì)等待.
如果理解阻塞等待?
針對(duì)每一把鎖 , 操作系統(tǒng)都會(huì)維護(hù)一個(gè)等待隊(duì)列 , 當(dāng)一個(gè)線程獲取到這個(gè)鎖之后 , 其他線性再嘗試獲取這個(gè)鎖 , 就會(huì)獲取不到鎖 , 陷入阻塞等待. 一直等到之前這個(gè)線程釋放鎖后 , 操作系統(tǒng)才會(huì)喚醒其他線程來(lái)再次競(jìng)爭(zhēng)這個(gè)鎖.
2)可重入
synchronized 對(duì)同一個(gè)線程來(lái)說(shuō)是可重入的 , 不會(huì)出現(xiàn)把自己鎖死的情況.
如何理解把自己鎖死?
觀察下面這段代碼可以發(fā)現(xiàn) , 當(dāng)某個(gè)線程調(diào)用add方法時(shí) , 就會(huì)對(duì) this 對(duì)象先加鎖 , 接著進(jìn)入代碼塊又會(huì)對(duì) this 對(duì)象再次嘗試加鎖. 站在 this 對(duì)象的角度 , 它認(rèn)為自己已經(jīng)被另外的線程占用了 , 那么第二次加鎖是否需要阻塞等待呢? 如果運(yùn)行上述情況 , 那么這個(gè)鎖就是可重入的 , 否則就是不可重入的.不可重入鎖會(huì)導(dǎo)致出現(xiàn)死鎖 , 而Java中的 synchronized 是可重入鎖 , 因此沒(méi)有上述問(wèn)題.
synchronized public void add(){ synchronized (this) { count++; } }
在可重入鎖內(nèi)部 , 包含了"線程持有者"和"計(jì)數(shù)器"兩個(gè)信息.
- 如果每個(gè)線程加鎖時(shí) , 發(fā)現(xiàn)鎖以及被占用了 , 但加鎖的人是它自己 , 那么仍然可以獲取到鎖 , 讓計(jì)數(shù)器自增.
- 解鎖的時(shí)候當(dāng)計(jì)數(shù)器遞減到0時(shí) , 才真正釋放鎖.
2.synchronized使用示例:
1). 修飾普通方法
鎖的是 Counter 對(duì)象.
class Counter{ public int count; synchronized public void add(){ count++; } }
2). 修飾靜態(tài)方法
鎖的是 Counter 類
class Counter{ public int count; synchronized public static void add(){ count++; } }
3).修飾代碼塊.明確指定鎖哪個(gè)對(duì)象
鎖當(dāng)前對(duì)象:
class Counter{ public int count; public void add(){ synchronized (this) { count++; } } }
鎖類對(duì)象:
class Counter{ public int count; public void add(){ synchronized (Counter.class) { count++; } } }
類鎖和對(duì)象鎖有什么區(qū)別?
顧名思義 , 對(duì)象鎖用來(lái)鎖住當(dāng)前對(duì)象 , 類鎖用來(lái)鎖住當(dāng)前類.如果一個(gè)類有多個(gè)實(shí)例對(duì)象 , 那么如果對(duì)其中一個(gè)對(duì)象加鎖 , 別的線程只會(huì)在訪問(wèn)這個(gè)對(duì)象時(shí)阻塞等待 , 訪問(wèn)其他對(duì)象時(shí)沒(méi)有影響.但如果是類鎖 , 那么當(dāng)一個(gè)線程對(duì)這個(gè)類加鎖后 , 其他線程訪問(wèn)該類的所有對(duì)象都要阻塞等待.
3.Java標(biāo)準(zhǔn)庫(kù)中的線程安全類
Java 標(biāo)準(zhǔn)庫(kù)中有很多線程是不安全的 , 這些類可能涉及多線程修改共享數(shù)據(jù) , 卻又沒(méi)有任何加鎖措施.
- ArrayList
- LinkedList
- HashMap
- HashSet
- TreeSet
- StringBuilder
但還有一些是線程安全的 , 使用一些鎖機(jī)制來(lái)控制.
- Vector
- HashTable
- CurrentHashMap
- StringBuffer
@Override @IntrinsicCandidate public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
這些線程之所以不加鎖是因?yàn)?, 加鎖會(huì)損失部分性能.
4.死鎖是什么
死鎖是這樣一種情況 , 多個(gè)線程同時(shí)被阻塞 , 其中一個(gè)或全部都在等待某個(gè)資源被釋放.由于線程被無(wú)限期的阻塞 , 因此程序不可能正常終止.
死鎖的三個(gè)典型情況
1). 一個(gè)線程一把鎖 , 連續(xù)加兩次 , 如果鎖是不可重入鎖 , 就會(huì)死鎖. Java中的synchronized和ReentranLock 都是可重入鎖 , 因此不會(huì)出現(xiàn)上述問(wèn)題.
2). 兩個(gè)線程兩把鎖 , t1 和 t2 線程各種先針對(duì)鎖A和鎖B加鎖 , 再嘗試獲取對(duì)方的鎖.
例如 , 張三和女神去吃餃子 , 需要蘸醋和醬油 , 張三拿到醋 , 女神拿到醬油 , 張三對(duì)女神說(shuō):"你先把醬油給我 , 我用完就把醋給你" , 女神對(duì)張三說(shuō):"你先把醋給我 , 我用完就把醬油給你". 這時(shí)兩人爭(zhēng)執(zhí)不下 , 就構(gòu)成了死鎖 , 醋和醬油就是兩把鎖 , 張三和女生就是兩個(gè)線程.
public static void main(String[] args) { Object jiangyou = new Object(); Object cu = new Object(); Thread zhangsan = new Thread(()->{ synchronized (jiangyou){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (cu ){ System.out.println("張三把醬油和醋都拿到了"); } } }); Thread nvsheng = new Thread(()->{ synchronized (cu ){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (jiangyou){ System.out.println("女神把醬油和醋都拿到了"); } } }); zhangsan.start(); nvsheng.start(); }
執(zhí)行代碼后 , 發(fā)現(xiàn)沒(méi)有打印任何日志 , 說(shuō)明沒(méi)有線程拿到兩把鎖.
通過(guò)jconsole查看線程的情況:
3)多個(gè)線程多把鎖
例如常見(jiàn)經(jīng)典案例--"哲學(xué)家就餐問(wèn)題"
假設(shè)有五個(gè)哲學(xué)家圍著桌子吃飯 , 每個(gè)人中間放一個(gè)筷子 , 哲學(xué)家有兩種狀態(tài) , 1.思考人生(相當(dāng)于線程的阻塞狀態(tài)) , 2.拿起筷子吃面條(相當(dāng)于線程獲取到鎖執(zhí)行計(jì)算) , 由于操作系統(tǒng)的隨機(jī)調(diào)度 , 這五個(gè)哲學(xué)家隨時(shí)都可能想吃面條 , 也隨時(shí)都可能思考人生 , 但是想要吃面條就得同時(shí)拿起左右兩個(gè)筷子.
假設(shè)同一時(shí)刻 , 所有哲學(xué)家同時(shí)拿起左手的筷子 , 所有的哲學(xué)家都拿不起右手的筷子 , 就會(huì)產(chǎn)生死鎖.
死鎖是一個(gè)嚴(yán)重的"BUG" , 導(dǎo)致一個(gè)程序的線程"卡死"無(wú)法正常工作.
5.如果避免死鎖
死鎖的四個(gè)必要條件:
1.互斥使用: 當(dāng)資源被一個(gè)線程占有時(shí) , 別的線程不能使用
2.不可搶占: 資源請(qǐng)求者不能從資源獲取者手中奪取資源 , 只能等資源占有者主動(dòng)釋放.
3.請(qǐng)求和保持: 當(dāng)資源請(qǐng)求者請(qǐng)求獲取別的資源時(shí) , 保存對(duì)原有資源的占有.
4.循環(huán)等待: 即存在一個(gè)等待隊(duì)列 , P1占有P2的資源 , P2占有P3的資源 , P3占有P1的資 源, 這樣就形成一個(gè)等待回路.
當(dāng)上述四個(gè)條件都成立就會(huì)形成死鎖 , 當(dāng)然破壞其中一個(gè)條件也可以打破死鎖 , 對(duì)于synchronized 來(lái)說(shuō) , 前三個(gè)條件是鎖的基本特性 , 因此想要打破死鎖只能從"循環(huán)等待"入手.
如何破除死鎖?
如果我們給鎖編號(hào) , 然后指定一個(gè)固定的順序來(lái)加鎖(必然從小到大) , 任意線程加多把鎖的時(shí)候都遵循上述順序, 此時(shí)循環(huán)等待自然破除.
因此解決哲學(xué)家就餐問(wèn)題就可以給每個(gè)筷子編號(hào) , 每個(gè)人都遵守"先拿小的再拿大的順序".此時(shí)1號(hào)哲學(xué)家和2號(hào)哲學(xué)家為了競(jìng)爭(zhēng)筷子其中一個(gè)人就會(huì)阻塞等待 , 這時(shí)5號(hào)哲學(xué)家就有了可乘之機(jī) , 5號(hào)哲學(xué)家拿起4號(hào)和5號(hào)筷子吃完面條 , 四號(hào)哲學(xué)家重復(fù)上述操作也吃完面條 , 這樣就完美的打破了循環(huán)等待的問(wèn)題.
同樣 , 最初的張三和女神吃餃子問(wèn)題也是同樣的解決方式 , 規(guī)定兩人都按"先拿醋再拿餃子"的順序執(zhí)行 , 就可以完美解決死鎖問(wèn)題.
public static void main(String[] args) { Object jiangyou = new Object(); Object cu = new Object(); Thread zhangsan = new Thread(()->{ synchronized (cu){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (jiangyou ){ System.out.println("張三把醬油和醋都拿到了"); } } }); Thread nvsheng = new Thread(()->{ synchronized (cu ){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (jiangyou){ System.out.println("女神把醬油和醋都拿到了"); } } }); zhangsan.start(); nvsheng.start(); }
到此這篇關(guān)于Java synchronized與死鎖深入探究的文章就介紹到這了,更多相關(guān)Java synchronized 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java同步鎖Synchronized底層源碼和原理剖析(推薦)
- java同步鎖的正確使用方法(必看篇)
- 95%的Java程序員人都用不好Synchronized詳解
- Java?synchronized同步關(guān)鍵字工作原理
- Java synchronized偏向鎖的概念與使用
- Java?synchronized輕量級(jí)鎖實(shí)現(xiàn)過(guò)程淺析
- Java synchronized重量級(jí)鎖實(shí)現(xiàn)過(guò)程淺析
- Java @Transactional與synchronized使用的問(wèn)題
- Java synchronized與CAS使用方式詳解
- 淺析Java關(guān)鍵詞synchronized的使用
- synchronized及JUC顯式locks?使用原理解析
- java鎖synchronized面試常問(wèn)總結(jié)
- Java?HashTable與Collections.synchronizedMap源碼深入解析
- Java?Synchronized鎖的使用詳解
- AQS加鎖機(jī)制Synchronized相似點(diǎn)詳解
- Java必會(huì)的Synchronized底層原理剖析
- 一個(gè)例子帶你看懂Java中synchronized關(guān)鍵字到底怎么用
- 詳解Java?Synchronized的實(shí)現(xiàn)原理
- Synchronized?和?ReentrantLock?的實(shí)現(xiàn)原理及區(qū)別
- Java同步鎖synchronized用法的最全總結(jié)
相關(guān)文章
SpringBoot2零基礎(chǔ)到精通之JUnit 5與指標(biāo)監(jiān)控
SpringBoot是一種整合Spring技術(shù)棧的方式(或者說(shuō)是框架),同時(shí)也是簡(jiǎn)化Spring的一種快速開(kāi)發(fā)的腳手架,本篇讓我們一起學(xué)習(xí)JUnit 5與指標(biāo)監(jiān)控2022-03-03Hibernate映射之基本類映射和對(duì)象關(guān)系映射詳解
這篇文章主要介紹了Hibernate映射之基本類映射和對(duì)象關(guān)系映射詳解,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05SpringBoot同一接口多個(gè)實(shí)現(xiàn)類配置的實(shí)例詳解
這篇文章主要介紹了SpringBoot同一接口多個(gè)實(shí)現(xiàn)類配置的實(shí)例詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11SpringBoot+Redis海量重復(fù)提交問(wèn)題解決
在實(shí)際的開(kāi)發(fā)項(xiàng)目中,一個(gè)對(duì)外暴露的接口往往會(huì)面臨很多次請(qǐng)求,所以本文介紹一下SpringBoot+Redis海量重復(fù)提交問(wèn)題解決,感興趣的可以了解一下2023-12-12將字符串?dāng)?shù)字格式化為樣式1,000,000,000的方法
這篇文章主要介紹了將字符串?dāng)?shù)字格式化為樣式1,000,000,000的方法,有需要的朋友可以參考一下2014-01-01java8新特性-lambda表達(dá)式入門學(xué)習(xí)心得
這篇文章主要介紹了java8新特性-lambda表達(dá)式入門學(xué)習(xí)心得,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03JAVA?ServLet創(chuàng)建一個(gè)項(xiàng)目的基本步驟
Servlet是Server Applet的簡(jiǎn)稱,是運(yùn)行在服務(wù)器上的小程序,用于編寫Java的服務(wù)器端程序,它的主要作用是接收并響應(yīng)來(lái)自Web客戶端的請(qǐng)求,下面這篇文章主要給大家介紹了關(guān)于JAVA?ServLet創(chuàng)建一個(gè)項(xiàng)目的基本步驟,需要的朋友可以參考下2024-03-03