Java线程 - 死锁的解决方案?

时间:2013-12-10 09:28:12

标签: java multithreading thread-safety deadlock

我已经汇总了一些Java代码,它们演示了线程中的死锁。就它本身而言,我通常得到2行输出和一个例外,有时在输出行之前和有时之后是预期的。我得到的例外是transfer()方法第一行的NullPointerException。

我遇到的问题是我想知道如何解决这个死锁问题。我已经在StackOverflow上搜索了这个问题并找到了这个页面:

Avoid Deadlock example

作为解决方案,我尝试了Will Hartung和Dreamcash发布的内容,但在尝试使用synchronize或ReentrantLock对象时仍然会遇到异常。

以下是代码:

帐户类

public class Account {

    int id;
    double balance;

    public Account(int id, double balance){
        this.id = id;
        this.balance = balance;
    }

    public void withdraw(double amount){
        balance = balance - amount;
    }

    public void deposit(double amount){
        balance = balance + amount;
    }

    public int getID(){
        return id;
    }

    public double getBalance(){
        return balance;
    }

}

银行类(单身人士):

public class Bank{

    static Bank bank;

    Account a1;
    Account a2;

    private Bank(){}

    public static Bank getInstance(){
        if(bank==null){
            bank = new Bank();
            bank.setAccountOne(new Account(1, 100));
            bank.setAccountTwo(new Account(2, 100));
        }

        return bank;
    }


    public void transfer(Account from, Account to, double amount){
        from.withdraw(amount);
        to.deposit(amount);
    }


    public Account getAccountOne(){
        return a1;
    }

    public Account getAccountTwo(){
        return a2;
    }
    public void setAccountOne(Account acc){
        a1 = acc;
    }
    public void setAccountTwo(Account acc){
        a2 = acc;
    }

}

PersonOne班级:

public class PersonOne implements Runnable {

    public void run() {

        Bank bank = Bank.getInstance();     

        Account a1 = bank.getAccountOne();
        Account a2 = bank.getAccountTwo();

        bank.transfer(a2, a1, 10);      

        System.out.println("T1: New balance of A1 is " + a1.getBalance());
        System.out.println("T1: New balance of A2 is " + a2.getBalance());
    }

}

PersonTwo类:

public class PersonTwo implements Runnable {

    public void run() {
        Bank bank = Bank.getInstance();
        Account a1 = bank.getAccountOne();
        Account a2 = bank.getAccountTwo();

        bank.transfer(a1, a2, 10);

        System.out.println("T2: New balance of A1 is " + a1.getBalance());
        System.out.println("T2: New balance of A2 is " + a2.getBalance());
    }

}

最后,我的主要方法

    public static void main(String[] args){
        PersonOne p1 = new PersonOne();
        PersonTwo p2 = new PersonTwo();

        Thread t1 = new Thread(p1);
        Thread t2 = new Thread(p2);

        t1.start();
        t2.start();
    }

3 个答案:

答案 0 :(得分:8)

  

我得到的异常是transfer()方法第一行的NullPointerException。

     

我遇到的问题是我想知道如何解决这个死锁问题。

您的代码无法引发任何死锁。它引起的问题是写入可见性问题:其中一个线程调用惰性Bank初始化程序而另一个线程看不到写入。

要获得死锁,首先需要任何类型的锁定(synchronized关键字)。您的特定NPE问题将通过向synchronized方法添加getInstance来解决,并且不会引入任何死锁。

我的结论是,你最好的办法是阅读一些关于Java并发性的介绍性材料。

答案 1 :(得分:2)

有许多解决方案,其中一些不太明显的解决方案

  • 使用一个线程,不要使用锁。在这个例子中,由于锁的开销超过了正在完成的工作,因此代码对我来说简单得多,而且更快只有一个线程。
  • 因为这只是一个例子,你只使用一个全局锁。这不会像多个锁一样好,但它更简单,如果你不需要性能,你应该做的更简单,不太可能得到错误。这不会陷入僵局。
  • 如果您必须使用多重锁定,因为这是作业,您可以确保始终锁定相同的顺序。您可以通过对唯一键上的对象进行排序并始终先锁定“第一个”项来完成此操作。
  • 最后,您可以通过在第二个帐户上使用tryLock以任何顺序锁定对象。如果失败,请释放两个锁,然后重试。您可以使用Unsafe.tryMonitor()在监视器上执行tryLock。

答案 2 :(得分:0)

感谢您的回答。

我目前正在学习线程。在上面,我故意试图创建一个死锁,以便我可以学习如何解决死锁问题(如果我真的想要一个执行上述操作的程序,我会避免多线程)。

我使用单例的原因是两个线程试图使用相同的对象。


所以...上面是我用来尝试重新创建死锁的代码,所以我在运行时期待出现问题/异常。我尝试修复它的一种方法是将传输方法重写为:

public void transfer(Account from, Account to, double amount){      
    synchronized(from){
        synchronized(to){
            from.withdraw(amount);
            to.withdraw(amount);
        }
    }
}

但是我会在synchronized(from)行上得到NullPointerException。

我也在传输方法中尝试了这个(并且帐户里面有一个ReentrantLock对象)。但是这次我在读取from.getLock()的行上得到一个NullPointerException。unlock()

public void transfer(Account from, Account to, double amount){      
    while(true){
        try{
            if(from.getLock().tryLock()){
                try{
                    if(to.getLock().tryLock()){
                        from.withdraw(amount);
                        to.withdraw(amount);
                        break;
                    }
                } finally {
                    to.getLock().unlock();
                }               
            }           
        } finally{
            from.getLock().unlock();
        }

        Random random = new Random();
        int i = random.nextInt(1000);
        try {
            Thread.sleep(1000 + i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}