在JAVA并發(fā)編程中幔崖,我們使用鎖來確毙湃希可變共享變量的安全性。要注意的是青责,不正確的使用鎖很容易導致死鎖挺据。本篇文章轉(zhuǎn)載自:JAVA并發(fā)-3種典型的死鎖
一、死鎖產(chǎn)生的條件
一般來說脖隶,要出現(xiàn)死鎖問題需要滿足以下條件:
- 互斥條件:一個資源每次只能被一個線程使用扁耐。
- 請求與保持條件:一個線程因請求資源而阻塞時,對已獲得的資源保持不放产阱。
- 不剝奪條件:線程已獲得的資源婉称,在未使用完之前,不能強行剝奪。
- 循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關系王暗。
在JAVA編程中榨乎,有3種典型的死鎖類型:
靜態(tài)的鎖順序死鎖,動態(tài)的鎖順序死鎖瘫筐,協(xié)作對象之間發(fā)生的死鎖。
二铐姚、靜態(tài)的鎖順序死鎖
a和b兩個方法都需要獲得A鎖和B鎖策肝。一個線程執(zhí)行a方法且已經(jīng)獲得了A鎖,在等待B鎖隐绵;另一個線程執(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)的鎖順序死鎖的方法就是:所有需要多個鎖的線程,都要以相同的順序來獲得鎖峭跳。 **
//正確的代碼
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");
}
}
}
}
三膘婶、動態(tài)的鎖順序死鎖:
動態(tài)的鎖順序死鎖是指兩個線程調(diào)用同一個方法時,傳入的參數(shù)顛倒造成的死鎖蛀醉。如下代碼悬襟,一個線程調(diào)用了transferMoney方法并傳入?yún)?shù)accountA,accountB;另一個線程調(diào)用了transferMoney方法并傳入?yún)?shù)accountB,accountA拯刁。此時就可能發(fā)生在靜態(tài)的鎖順序死鎖中存在的問題脊岳,即:第一個線程獲得了accountA鎖并等待accountB鎖,第二個線程獲得了accountB鎖并等待accountA鎖垛玻。
//可能發(fā)生動態(tài)鎖順序死鎖的代碼
class DynamicLockOrderDeadLock{
public void transefMoney(Account fromAccount,Account toAccount,Double amount){
synchronized (fromAccount) {
synchronized (toAccount) {
//...
fromAccount.minus(amount);
toAccount.add(amount);
//...
}
}
}
}
**動態(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é)作對象之間發(fā)生的死鎖:
有時帚桩,死鎖并不會那么明顯亿驾,比如兩個相互協(xié)作的類之間的死鎖,比如下面的代碼:一個線程調(diào)用了Taxi對象的setLocation方法朗儒,另一個線程調(diào)用了Dispatcher對象的getImage方法颊乘。此時可能會發(fā)生,第一個線程持有Taxi對象鎖并等待Dispatcher對象鎖醉锄,另一個線程持有Dispatcher對象鎖并等待Taxi對象鎖乏悄。
//可能發(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對象鎖
}
}
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對象鎖
return image;
}
}
上面的代碼中檩小,我們在持有鎖的情況下調(diào)用了外部的方法,這是非常危險的(可能發(fā)生死鎖)烟勋。為了避免這種危險的情況發(fā)生规求,我們使用開放調(diào)用筐付。如果調(diào)用某個外部方法時不需要持有鎖,我們稱之為開放調(diào)用阻肿。
**解決協(xié)作對象之間發(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)的鎖順序死鎖赴邻,動態(tài)的鎖順序死鎖印衔,協(xié)作對象之間的死鎖。在寫代碼時姥敛,要確保線程在獲取多個鎖時采用一致的順序奸焙。同時,要避免在持有鎖的情況下調(diào)用外部方法彤敛。