一奥喻、什么是死鎖(deadlock)偶宫?
死鎖是因為使用了加鎖機制所引發(fā)的。是指兩個或兩個以上的進程在執(zhí)行過程中环鲤,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象纯趋,若無外力作用,它們都將無法推進下去冷离,此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖吵冒。
二、死鎖的必要條件
- 多個操作者(M>=2)操作多個資源(N>=2) M>=N
- 爭奪資源的順序不對
嚴(yán)格意義上來說:
1.互斥
2.不剝奪
3.請求和保持
4.循環(huán)等待
三西剥、代碼示例
/**
*類說明:演示普通賬戶的死鎖和解決
*/
public class NormalDeadLock {
private static Object valueFirst = new Object();//第一個鎖
private static Object valueSecond = new Object();//第二個鎖
//先拿第一個鎖痹栖,再拿第二個鎖
private static void fisrtToSecond() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (valueFirst){
System.out.println(threadName + " 1st");
Thread.sleep(100);
synchronized (valueSecond){
System.out.println(threadName + " 2nd");
}
}
}
//先拿第二個鎖,再拿第一個鎖
private static void SecondToFisrt() throws InterruptedException {
String threadName = Thread.currentThread().getName();
//TODO
synchronized (valueSecond){
System.out.println(threadName + " 2nd");
Thread.sleep(100);
synchronized (valueFirst){
System.out.println(threadName + " 1st");
}
}
}
private static class TestThread extends Thread{
private String name;
public TestThread(String name) {
this.name = name;
}
public void run(){
Thread.currentThread().setName(name);
try {
//先拿第一個鎖再拿第二個鎖
SecondToFisrt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread.currentThread().setName("TestDeadLock");
TestThread testThread = new TestThread("SubTestThread");
testThread.start();
try {
fisrtToSecond();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運行結(jié)果:
死鎖
可以看到瞭空,兩個線程僵持著结耀,程序沒有死,兩個線程都沒有做事匙铡,阻塞了图甜,而且沒有打印任何的異常信息。你只會感覺到程序越來越慢鳖眼。死鎖一旦發(fā)生黑毅,問題的定位是很難的。
但是我們用的idea钦讳,可以看到如下的堆棧信息:
堆棧信息
可以看到一個線程持有了一把鎖矿瘦,在等待另一個鎖。但是另一個線程持有的剛好是另一個線程需要的鎖愿卒,等待的也是另一個線程持有的鎖缚去,他們互不釋放,因此產(chǎn)生了死鎖琼开。
在生產(chǎn)環(huán)境中易结,我們可以用jdk提供的jstack命令去觀察線程的堆棧。參考:https://www.cnblogs.com/wuchanming/p/7766994.html
四柜候、解決方法
如果我們知道線程的鎖的順序搞动,直接調(diào)整鎖的順序即可,但是如果是動態(tài)的場景渣刷,我們很難去發(fā)現(xiàn)線程鎖的順序鹦肿。這種情況我們有兩種解決方法:
- 增加一個第三個鎖,先比較第一個和第二個鎖的哈希值辅柴,如果雙方?jīng)]有比較出大小箩溃,就鎖第三個鎖瞭吃,直到兩把鎖比較出大小為止。相當(dāng)于NBA必須有輸贏
/**
*
*類說明:不會產(chǎn)生死鎖的安全轉(zhuǎn)賬
* 誰的hash在前涣旨,就先鎖誰
*/
public class SafeOperate implements ITransfer {
private static Object tieLock = new Object();//第三把鎖
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if(fromHash<toHash){
synchronized (from){
System.out.println(Thread.currentThread().getName()+" get "+from.getName());
Thread.sleep(100);
synchronized (to){
System.out.println(Thread.currentThread().getName()+" get "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
}
}
}else if(toHash<fromHash){
synchronized (to){
System.out.println(Thread.currentThread().getName()+" get"+to.getName());
Thread.sleep(100);
synchronized (from){
System.out.println(Thread.currentThread().getName()+" get"+from.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
}
}
}else{
synchronized (tieLock){
synchronized (from){
synchronized (to){
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
}
}
- 使用tryLock()機制
/**
*
*類說明:不會產(chǎn)生死鎖的安全轉(zhuǎn)賬第二種方法
* 嘗試拿鎖
*/
public class SafeOperateToo implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
Random r = new Random();
while(true){
if(from.getLock().tryLock()){
System.out.println(Thread.currentThread().getName()
+" get"+from.getName());
try{
if(to.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName()
+" get"+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
break;
}finally{
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
//為什么要休眠兩毫秒歪架?拿鎖的過程會很長,反復(fù)地拿鎖开泽,這種情況會造成CPU的浪費牡拇。
//有A,B兩個線程魁瞪,都需要去拿c,d兩把鎖
//A持有了c鎖穆律,同事B持有了d鎖;A想要獲取d鎖导俘,于是去嘗試峦耘,但是B想要獲取c鎖,于是也去嘗試旅薄,嘗試結(jié)束之后如果沒有獲取到鎖的話辅髓,就將自己持有的鎖釋放掉,但是釋放之后另一個需要相應(yīng)鎖的線程并不知道
//然后接著又拿起自己的鎖去嘗試少梁。洛口。。凯沪。又去釋放第焰,造成了資源的浪費。
//這種情況叫活鎖妨马,讓拿鎖的時機稍微錯開一點點挺举,打斷了拿鎖和釋放鎖之間的碰撞情況
Thread.sleep(r.nextInt(2));
}
}
}
活鎖(為什么需要休眠兩毫秒?)
拿鎖的過程會很長烘跺,反復(fù)地拿鎖湘纵,這種情況會造成CPU的浪費。
有A,B兩個線程滤淳,都需要去拿c,d兩把鎖梧喷。A持有了c鎖,同事B持有了d鎖脖咐;A想要獲取d鎖伤柄,于是去嘗試,但是B想要獲取c鎖文搂,于是也去嘗試适刀,嘗試結(jié)束之后如果沒有獲取到鎖的話,就將自己持有的鎖釋放掉煤蹭,但是釋放之后另一個需要相應(yīng)鎖的線程并不知道笔喉。然后接著又拿起自己的鎖去嘗試......又去釋放取视,這樣一直循環(huán)下去。造成了資源的浪費常挚。
這種情況叫活鎖作谭,讓拿鎖的時機稍微錯開一點點,打斷了拿鎖和釋放鎖之間的碰撞情況
五奄毡、線程饑餓
低優(yōu)先級的線程總是拿不到執(zhí)行時間以至于這個線程一直干等著得不到執(zhí)行折欠。