欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java并发之死锁

程序员文章站 2022-04-18 11:31:01
...

前言

在Java编程中,有3种典型的死锁类型:静态的锁顺序死锁、动态的锁顺序死锁、协作对象之间的死锁

一、静态的锁顺序死锁

a、b两个方法都要获得A锁和B锁。线程1执行a方法获得了A锁,在等待B锁;线程2执行了b方法获得了B锁,在等待A锁。

/**
*可能会发生静态的锁顺序死锁
*/
public class StaticLockOrderDeadLock {
    private final Object lockA = new Object();
    private final Object lockB = new Object();
    private void a(){
        synchronized (lockA){
            synchronized (lockB){
                System.out.println("方法a");
            }
        }
    }
    private void b(){
        synchronized (lockB){
            synchronized (lockA){
                System.out.println("方法b");
            }
        }
    }
}

**解决方式:**所有需要多个锁的线程,都要以相同的顺序获得锁。

/**
*这样就能避免了
*/
public class StaticLockOrderDeadLock {
    private final Object lockA = new Object();
    private final Object lockB = new Object();
    private void a(){
        synchronized (lockA){
            synchronized (lockB){
                System.out.println("方法a");
            }
        }
    }
    private void b(){
        synchronized (lockA){
            synchronized (lockB){
                System.out.println("方法b");
            }
        }
    }
}

二、动态的锁顺序死锁

两个线程调用同一个方法时,传入的参数颠倒造成的死锁

//两个线程调用此方法,参数传反就会发生动态的锁顺序死锁
//线程1获得了accountA锁,等待accountB锁。线程2获得了accountB锁,等待accountA锁
public class DynamicLockOrderDeadLock {
    public void transefMoney(Account fromAccount, Account toAccount,Double amount){
        synchronized (fromAccount){
            synchronized (toAccount){
                ...
                fromAccount.minus(amount);
                toAccount.add(amount);
                ...
            }
        }
    }
}

使用System.identityHashCode来定义锁的顺序,确保所有线程都能以相同的方式获得锁

public class DynamicLockOrderDeadLock {
    private final Object myLock = new Object();
    public void transefMoney(Account fromAccount, Account toAccount,Double amount){
        class Helper{
            private 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();
                    }
                }
            }
        }

    }
}

三、协作对象之间发生的死锁

两个相互协作的类之间,发生的死锁。一个线程调用Taxi对象的setLocation(),另一个线程调用了Dispatcher对象的getImg()。有可能线程1持有Taxi对象锁并等待Dispatcher对象锁,而线程2持有Dispatcher对象锁而等待Taxi对象。

public 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);//外部调用,可能会等待Dispatcher对象锁
    }
}
public 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 getImg(){
        Image image = new Image();
        for (Taxi t:taxis){
            image.drawMarker(t.getLocation());//外部调用方法,可能要等待Taxi对象锁
            return image;
        }
    }
}

在持有锁的前提下,就调用了外部方法极易发生死锁。需要作出调整,调用外部方法时就不再需要持有锁,即开放调用。

public 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);//开放调用
    }
}
public 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 getImg(){
        Set<Taxi> copy;
        synchronized (this){
            copy = new HashSet<>(taxis);
        }
        Image image = new Image();
        for (Taxi t:copy){
            image.drawMarker(t.getLocation());//开放调用
            return image;
        }
    }
}

总结死锁的产生条件

1.互斥条件:一个资源每次只能被一个线程使用
2.请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
3.不剥夺条件:线程已获得资源,在未使用完之前,不能强行剥夺
4.循环等待条件:若干线程之间形成一种首尾相接循环等待资源关系

相关标签: 死锁