一峡钓、什么是死鎖
死鎖不僅在個(gè)人學(xué)習(xí)中妓笙,甚至在開(kāi)發(fā)中也并不常見(jiàn)。但是一旦出現(xiàn)死鎖能岩,后果將非常嚴(yán)重寞宫。
首先什么是死鎖呢?打個(gè)比方捧灰,就好像有兩個(gè)人打架淆九,互相限制住了(鎖住,抱住)彼此一樣毛俏,互相動(dòng)彈不得,而且互相歐氣饲窿,你不松手我就不松手煌寇。好了誰(shuí)也動(dòng)彈不得。
在多線程的環(huán)境下逾雄,勢(shì)必會(huì)對(duì)資源進(jìn)行搶奪阀溶。當(dāng)兩個(gè)線程鎖住了當(dāng)前資源,但都需要對(duì)方的資源才能進(jìn)行下一步操作鸦泳,這個(gè)時(shí)候兩方就會(huì)一直等待對(duì)方的資源釋放银锻。這就形成了死鎖。這些永遠(yuǎn)在互相等待的進(jìn)程稱(chēng)為死鎖進(jìn)程做鹰。
那么我們來(lái)總結(jié)一下死鎖產(chǎn)生的條件:
互斥:資源的鎖是排他性的击纬,加鎖期間只能有一個(gè)線程擁有該資源。其他線程只能等待鎖釋放才能?chē)L試獲取該資源钾麸。
請(qǐng)求和保持:當(dāng)前線程已經(jīng)擁有至少一個(gè)資源更振,但其同時(shí)又發(fā)出新的資源請(qǐng)求,而被請(qǐng)求的資源被其他線程擁有饭尝。此時(shí)進(jìn)入保持當(dāng)前資源并等待下個(gè)資源的狀態(tài)肯腕。
不剝奪:線程已擁有的資源,只能由自己釋放钥平,不能被其他線程剝奪实撒。
循環(huán)等待:是指有多個(gè)線程互相的請(qǐng)求對(duì)方的資源,但同時(shí)擁有對(duì)方下一步所需的資源涉瘾。形成一種循環(huán)知态,類(lèi)似2)請(qǐng)求和保持。但此處指多個(gè)線程的關(guān)系睡汹。并不是指單個(gè)線程一直在循環(huán)中等待肴甸。
什么?還是不理解囚巴?那我們直接上代碼原在,動(dòng)手寫(xiě)一個(gè)死鎖友扰。
二、寫(xiě)一個(gè)死鎖
根據(jù)條件庶柿,我們讓兩個(gè)線程互相請(qǐng)求保持村怪。
/**
* 模擬死鎖場(chǎng)景
*/
public class DeadLockDemo implements Runnable{
? ? public static int flag = 1;
? ? //static 變量是 類(lèi)對(duì)象共享的
? ? static Object o1 = new Object();
? ? static Object o2 = new Object();
? ? @Override
? ? public void run() {
? ? ? ? System.out.println(Thread.currentThread().getName() + ":此時(shí) flag = " + flag);
? ? ? ? if(flag == 1){
? ? ? ? ? ? synchronized (o1){
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? System.out.println("我是" + Thread.currentThread().getName() + "鎖住 o1");
? ? ? ? ? ? ? ? ? ? Thread.sleep(3000);
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "醒來(lái)->準(zhǔn)備獲取 o2");
? ? ? ? ? ? ? ? }catch (Exception e){
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? synchronized (o2){
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "拿到 o2");//第24行
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if(flag == 0){
? ? ? ? ? ? synchronized (o2){
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? System.out.println("我是" + Thread.currentThread().getName() + "鎖住 o2");
? ? ? ? ? ? ? ? ? ? Thread.sleep(3000);
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "醒來(lái)->準(zhǔn)備獲取 o1");
? ? ? ? ? ? ? ? }catch (Exception e){
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? synchronized (o1){
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "拿到 o1");//第38行
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? public static? void main(String args[]){
? ? ? ? DeadLockDemo t1 = new DeadLockDemo();
? ? ? ? DeadLockDemo t2 = new DeadLockDemo();
? ? ? ? t1.flag = 1;
? ? ? ? new Thread(t1).start();
? ? ? ? //讓main線程休眠1秒鐘,保證t2開(kāi)啟鎖住o2.進(jìn)入死鎖
? ? ? ? try {
? ? ? ? ? ? Thread.sleep(1000);
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? t2.flag = 0;
? ? ? ? new Thread(t2).start();
? ? }
代碼中,
t1創(chuàng)建浮庐,t1先拿到o1的鎖甚负,開(kāi)始休眠3秒。然后
t2線程創(chuàng)建审残,t2拿到o2的鎖梭域,開(kāi)始休眠3秒。然后
t1先醒來(lái)搅轿,準(zhǔn)備拿o2的鎖病涨,發(fā)現(xiàn)o2已經(jīng)加鎖,只能等待o2的鎖釋放璧坟。
t2后醒來(lái)既穆,準(zhǔn)備拿o1的鎖,發(fā)現(xiàn)o1已經(jīng)加鎖雀鹃,只能等待o1的鎖釋放幻工。
t1,t2形成死鎖。
我們查看運(yùn)行狀態(tài)
三黎茎、發(fā)現(xiàn)排查死鎖情況
我們利用jdk提供的工具定位死鎖問(wèn)題:
jps顯示所有當(dāng)前Java虛擬機(jī)進(jìn)程名及pid.
jstack打印進(jìn)程堆棧信息囊颅。
列出所有java進(jìn)程。
我們檢查一下DeadLockDemo工三,為什么這個(gè)線程不退棧迁酸。
jstack 11170
我們直接翻到最后:已經(jīng)檢測(cè)出了一個(gè)java級(jí)別死鎖。其中兩個(gè)線程分別卡在了代碼第27行和第41行俭正。檢查我們代碼的對(duì)應(yīng)位置奸鬓,即可排查錯(cuò)誤。此處我們是第二個(gè)鎖始終拿不到掸读,所以死鎖了串远。
在這里插播一下,還是有很多朋友對(duì)死鎖不了解儿惫,為了讓大家在之后的學(xué)習(xí)或者工作中避免出現(xiàn)死鎖不知道該如何解決澡罚。我在這里寫(xiě)一些常見(jiàn)的死鎖是怎么產(chǎn)生的便于大家避免,遇到相似的情況可以很好的解決肾请。后續(xù)也會(huì)持續(xù)更新Java的其它知識(shí)點(diǎn)和面試方法資料等等...感興趣的鐵汁們可以持續(xù)關(guān)注我
常見(jiàn)的死鎖是如何產(chǎn)生的留搔,如何避免
概述:
什么場(chǎng)景下回發(fā)生 00060 死鎖問(wèn)題:
一般情況下,數(shù)據(jù)庫(kù)自身發(fā)生死鎖的情況很少铛铁,一般情況都是因?yàn)閼?yīng)用本身調(diào)用問(wèn)題導(dǎo)致的 00060異常 隔显。
比如說(shuō)有兩個(gè)會(huì)話(huà)sid却妨,分別為 138 和136,這兩個(gè)會(huì)話(huà)都要對(duì) 6677 和 7788 兩個(gè)人加工資括眠,但是執(zhí)行的順序不一樣彪标,操作分別是:
-- 會(huì)話(huà)session號(hào) Session 1 (sid = 136), Session 2 (sid = 138)
-- 執(zhí)行的語(yǔ)句 Session 1 (sid = 136)
update emp set sal=sal+100 where empno=6677;
update emp set sal=sal+100 where empno=7788;
-- 執(zhí)行的語(yǔ)句 Session 2 (sid = 138)
update emp set sal=sal+100 where empno=7788;
update emp set sal=sal+100 where empno=6677;
ORA-00060:deadlock detected while waiting for resource
這樣我們就成功的觸發(fā)了一個(gè)ORA-00060掷豺。
出現(xiàn)這個(gè)問(wèn)題捞烟,我們可以查看Oracle日志,日志路徑:$ORACLE_BASE/diag/rdbms/org11/ora11/trace/alert*.log
可以從告警日志中看到很多類(lèi)似如下的日志:
ORA-00060:Deadlock detected.More info in file
ORA-00060:Deadlock detected.More info in file /home1/oracle/diag/rdbms/ora11g/ora11g/trace/ora11g_ora_14757.trc
我們看下對(duì)飲的trc日志当船,主要看Deadlock graph题画,其中:可以看到136和138互相死鎖,session 138(也就是 session2生年,sid=138)等著要 ROWID=AAAMfAAAAgAAA的行鎖婴程,而 session136(也就是 session1,sid=136)等著要ROWID=AAAMfPAAEAAAAgAAL 的行抱婉。
處理方式:
1. 給資源編號(hào),然后按照固定的順序進(jìn)行訪問(wèn)桌粉。
簡(jiǎn)單的來(lái)說(shuō)蒸绩,就是先改編號(hào)小的,在改編號(hào)大的铃肯。當(dāng)然患亿,反著來(lái)也可以。
-- 會(huì)話(huà)session號(hào) Session 1 (sid = 136)押逼, Session 2 (sid = 138)
update emp set sal=sal+100 where empno=6677;
update emp set sal=sal+100 where empno=7788;
Waiting...
update emp set sal=sal+100 where empno=7788;
commit/rollback;
update emp set sal=sal+100 where empno=6677;
在這里步藕,工資增加兩次,但是 session 2 被 session 1 阻塞了挑格,對(duì)于用戶(hù)體驗(yàn)來(lái)說(shuō)咙冗,感受不好。
如果 session 1 一直不結(jié)束事務(wù)漂彤,session 2 只能一直等下去雾消,這樣比deadlock 后,Oracle 程序本身出面調(diào)停還要糟糕挫望。
2. 可以在 select … for update nowait 語(yǔ)句測(cè)試一下需要更改的行是否被鎖定
如果沒(méi)有被鎖定立润,那這個(gè)語(yǔ)句會(huì)馬上給這行加鎖,如果已經(jīng)加鎖那就馬上返回:ORA-00054:resource busy and acquire with NOWAIT specified 媳板,如下表所示:
-- 會(huì)話(huà)session號(hào) Session 1 (sid = 136)桑腮, Session 2 (sid = 138)
select * from emp where empno in(6677,7788) for update nowait;
select * from emp where empno in(6677,7788) for update nowait;
ORA-00054:resource busy and acquire with NOWAIT specified
update emp set sal=sal+100 where empno=6677;
update emp set sal=sal+100 where empno=7788;
方法一和方法二都存在一定的問(wèn)題,特別是在ND代碼中如果使用方法二那么修改起來(lái)工作量太大蛉幸,但是如果我們不處理破讨,Oracle有自動(dòng)檢測(cè)死鎖并且回滾事務(wù)的功能丛晦,也就是說(shuō)之前的會(huì)話(huà)中136 和138 有一個(gè)會(huì)成功,一個(gè)會(huì)回滾添忘,返回失敗采呐,這樣就保證了數(shù)據(jù)的一致性。
總結(jié):
對(duì)應(yīng)上面兩處處理方式搁骑。感覺(jué)都不好斧吐,畢竟現(xiàn)網(wǎng)這種場(chǎng)景較少。而且這種死鎖不是永久性的一直卡死在這仲器,Oracle會(huì)檢測(cè)到這種死鎖的煤率,并且檢測(cè)到后會(huì)自己回滾,所以直接交給Oracle即可乏冀。
附相關(guān)的查詢(xún)SQL:
1.查詢(xún)死鎖:
select t2.username,t2.sid,t2.seria#,t2.logon_time
from v$locked_object t1,v$session t2
where t1.session_id = t2.sid
order by t2.logon_time;
2.根據(jù) sid 查詢(xún)對(duì)應(yīng)的SQL語(yǔ)句蝶糯,比如第一點(diǎn)查詢(xún)出 sid 為136 和138 的死鎖結(jié)果:
select sql_text
from v$session a,$sqltext_with_newlines b
where DECODE(a.sql_hash_value,0,prev_hash_value,sql_hash_value) = b.hash_value and a.sid in ('136','138')
order by piece;
3.查看處于等待狀態(tài)的SQL語(yǔ)句:
select a.spid,c.EVENT,b.LOGON_TIME,d.SQL_TEXT,a.PROGRAM
from v$process a,v$session b,v$session_wait c,v$sql d
where a.ADDR = b.PADDR and b.SID = c.SID
? and b.SQL_HASH_VALUE = D.HASH_VALUE
? and c.EVENT NOT LIKE '%SQL*Net%'
? and c.EVENT NOT LIKE '%smon%'
? and c.EVENT NOT LIKE '%jopq%'
? and c.EVENT NOT LIKE '%ipc%'
4.查看目前是否有“長(zhǎng)時(shí)間持有鎖未釋放”,必要的情況下可以用對(duì)應(yīng)的 command 殺死 session:
select I.BLOCK,ILMODE,I.REQUEST,I.TYPE,I.ID1,I.CTIME,s.SID,s.SERIAL#,
T.SQL_TEXT,p.SPID,'ALTER SYSTEM KILL SESSION'"||S.SID||','||s.serial#||"',' Command
from v$lock i,v$SESSION s,v$SQL t,v$process p
where I.ID1 in (select id1 from v$lock where block = 1)
? and I.SID = s.SID and (t.hash_value = DECODE(s.sql_hash_value,0,s.prev_hash_value,s.sql_hash_value))
? and t.address = DECODE(a.sql_hash_value,0,s.prev_hash_addr,s.sql_address))
? and p.ADDR = s.PADDR
order by I.ID1,I.CTIME desc;
5.殺死進(jìn)程(396為sid辆沦,60589為serial#)昼捍,該條可以結(jié)合第一點(diǎn)進(jìn)行:
alter system kill session '396,60589';
四、解決辦法
死鎖一旦發(fā)生肢扯,我們就無(wú)法解決了妒茬。所以我們只能避免死鎖的發(fā)生。
既然死鎖需要滿(mǎn)足四種條件蔚晨,那我們就從條件下手乍钻,只要打破任意規(guī)則即可。
(互斥)盡量少用互斥鎖铭腕,能加讀鎖银择,不加寫(xiě)鎖。當(dāng)然這條無(wú)法避免累舷。
(請(qǐng)求和保持)采用資源靜態(tài)分配策略(進(jìn)程資源靜態(tài)分配方式是指一個(gè)進(jìn)程在建立時(shí)就分配了它需要的全部資源).我們盡量不讓線程同時(shí)去請(qǐng)求多個(gè)鎖浩考,或者在擁有一個(gè)鎖又請(qǐng)求不到下個(gè)鎖時(shí),不保持等待笋粟,先釋放資源等待一段時(shí)間在重新請(qǐng)求怀挠。
(不剝奪)允許進(jìn)程剝奪使用其他進(jìn)程占有的資源。優(yōu)先級(jí)害捕。
(循環(huán)等待)盡量調(diào)整獲得鎖的順序绿淋,不發(fā)生嵌套資源請(qǐng)求。加入超時(shí)尝盼。
本文到此結(jié)束啦吞滞,喜歡的鐵子們可以點(diǎn)點(diǎn)贊和關(guān)注, 文章持續(xù)更新,也可以評(píng)論出你想看哪一塊技術(shù)裁赠。鐵子們的支持是我的動(dòng)力殿漠,創(chuàng)作離不開(kāi)鐵子們的支持,在此先感謝大家佩捞!