在JAVA并發(fā)編程中,我們使用鎖來確保可變共享變量的安全性。要注意的是,不正確的使用鎖很容易導(dǎo)致死鎖。本篇文章轉(zhuǎn)載自:JAVA并發(fā)-3種典型的死鎖
一、死鎖產(chǎn)生的條件
一般來說,要出現(xiàn)死鎖問題需要滿足以下條件:
- 互斥條件:一個(gè)資源每次只能被一個(gè)線程使用。
- 請(qǐng)求與保持條件:一個(gè)線程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
- 不剝奪條件:線程已獲得的資源,在未使用完之前,不能強(qiáng)行剝奪。
- 循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
在JAVA編程中,有3種典型的死鎖類型:
靜態(tài)的鎖順序死鎖,動(dòng)態(tài)的鎖順序死鎖,協(xié)作對(duì)象之間發(fā)生的死鎖。
二、靜態(tài)的鎖順序死鎖
a和b兩個(gè)方法都需要獲得A鎖和B鎖。一個(gè)線程執(zhí)行a方法且已經(jīng)獲得了A鎖,在等待B鎖;另一個(gè)線程執(zhí)行了b方法且已經(jīng)獲得了B鎖,在等待A鎖。這種狀態(tài),就是發(fā)生了靜態(tài)的鎖順序死鎖。
//可能發(fā)生靜態(tài)鎖順序死鎖的代碼
class StaticLockOrderDeadLock{
private final Object lockA=new Object();
private final Object lockB=new Object();
public void a(){
synchronized (lockA) {
synchronized (lockB) {
System.out.println("function a");
}
}
}
public void b(){
synchronized (lockB) {
synchronized (lockA) {
System.out.println("function b");
}
}
}
}
**解決靜態(tài)的鎖順序死鎖的方法就是:所有需要多個(gè)鎖的線程,都要以相同的順序來獲得鎖。 **
//正確的代碼
class StaticLockOrderDeadLock{
private final Object lockA=new Object();
private final Object lockB=new Object();
public void a(){
synchronized (lockA) {
synchronized (lockB) {
System.out.println("function a");
}
}
}
public void b(){
synchronized (lockA) {
synchronized (lockB) {
System.out.println("function b");
}
}
}
}
三、動(dòng)態(tài)的鎖順序死鎖:
動(dòng)態(tài)的鎖順序死鎖是指兩個(gè)線程調(diào)用同一個(gè)方法時(shí),傳入的參數(shù)顛倒造成的死鎖。如下代碼,一個(gè)線程調(diào)用了transferMoney方法并傳入?yún)?shù)accountA,accountB;另一個(gè)線程調(diào)用了transferMoney方法并傳入?yún)?shù)accountB,accountA。此時(shí)就可能發(fā)生在靜態(tài)的鎖順序死鎖中存在的問題,即:第一個(gè)線程獲得了accountA鎖并等待accountB鎖,第二個(gè)線程獲得了accountB鎖并等待accountA鎖。
//可能發(fā)生動(dòng)態(tài)鎖順序死鎖的代碼
class DynamicLockOrderDeadLock{
public void transefMoney(Account fromAccount,Account toAccount,Double amount){
synchronized (fromAccount) {
synchronized (toAccount) {
//...
fromAccount.minus(amount);
toAccount.add(amount);
//...
}
}
}
}
**動(dòng)態(tài)的鎖順序死鎖解決方案如下:使用System.identifyHashCode來定義鎖的順序。確保所有的線程都以相同的順序獲得鎖 **
//正確的代碼
class DynamicLockOrderDeadLock{
private final Object myLock=new Object();
public void transefMoney(final Account fromAccount,final Account toAccount,final Double amount){
class Helper{
public void transfer(){
//...
fromAccount.minus(amount);
toAccount.add(amount);
//...
}
}
int fromHash=System.identityHashCode(fromAccount);
int toHash=System.identityHashCode(toAccount);
if(fromHash<toHash){
synchronized (fromAccount) {
synchronized (toAccount) {
new Helper().transfer();
}
}
}else if(fromHash>toHash){
synchronized (toAccount) {
synchronized (fromAccount) {
new Helper().transfer();
}
}
}else{
synchronized (myLock) {
synchronized (fromAccount) {
synchronized (toAccount) {
new Helper().transfer();
}
}
}
}
}
}
四、協(xié)作對(duì)象之間發(fā)生的死鎖:
有時(shí),死鎖并不會(huì)那么明顯,比如兩個(gè)相互協(xié)作的類之間的死鎖,比如下面的代碼:一個(gè)線程調(diào)用了Taxi對(duì)象的setLocation方法,另一個(gè)線程調(diào)用了Dispatcher對(duì)象的getImage方法。此時(shí)可能會(huì)發(fā)生,第一個(gè)線程持有Taxi對(duì)象鎖并等待Dispatcher對(duì)象鎖,另一個(gè)線程持有Dispatcher對(duì)象鎖并等待Taxi對(duì)象鎖。
//可能發(fā)生死鎖
class Taxi{
private Point location,destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher=dispatcher;
}
public synchronized Point getLocation(){
return location;
}
public synchronized void setLocation(Point location){
this.location=location;
if(location.equals(destination))
dispatcher.notifyAvailable(this);//外部調(diào)用方法,可能等待Dispatcher對(duì)象鎖
}
}
class Dispatcher{
private final Set<Taxi> taxis;
private final Set<Taxi> availableTaxis;
public Dispatcher(){
taxis=new HashSet<Taxi>();
availableTaxis=new HashSet<Taxi>();
}
public synchronized void notifyAvailable(Taxi taxi){
availableTaxis.add(taxi);
}
public synchronized Image getImage(){
Image image=new Image();
for(Taxi t:taxis)
image.drawMarker(t.getLocation());//外部調(diào)用方法,可能等待Taxi對(duì)象鎖
return image;
}
}
上面的代碼中,我們?cè)诔钟墟i的情況下調(diào)用了外部的方法,這是非常危險(xiǎn)的(可能發(fā)生死鎖)。為了避免這種危險(xiǎn)的情況發(fā)生,我們使用開放調(diào)用。如果調(diào)用某個(gè)外部方法時(shí)不需要持有鎖,我們稱之為開放調(diào)用。
**解決協(xié)作對(duì)象之間發(fā)生的死鎖:需要使用開放調(diào)用,即避免在持有鎖的情況下調(diào)用外部的方法。 **
//正確的代碼
class Taxi{
private Point location,destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher=dispatcher;
}
public synchronized Point getLocation(){
return location;
}
public void setLocation(Point location){
boolean flag=false;
synchronized (this) {
this.location=location;
flag=location.equals(destination);
}
if(flag)
dispatcher.notifyAvailable(this);//使用開放調(diào)用
}
}
class Dispatcher{
private final Set<Taxi> taxis;
private final Set<Taxi> availableTaxis;
public Dispatcher(){
taxis=new HashSet<Taxi>();
availableTaxis=new HashSet<Taxi>();
}
public synchronized void notifyAvailable(Taxi taxi){
availableTaxis.add(taxi);
}
public Image getImage(){
Set<Taxi> copy;
synchronized (this) {
copy=new HashSet<Taxi>(taxis);
}
Image image=new Image();
for(Taxi t:copy)
image.drawMarker(t.getLocation());//使用開放調(diào)用
return image;
}
}
五、總結(jié)
綜上,是常見的3種死鎖的類型。即:靜態(tài)的鎖順序死鎖,動(dòng)態(tài)的鎖順序死鎖,協(xié)作對(duì)象之間的死鎖。在寫代碼時(shí),要確保線程在獲取多個(gè)鎖時(shí)采用一致的順序。同時(shí),要避免在持有鎖的情況下調(diào)用外部方法。