线程死锁避免

时间:2016-10-08 21:05:16

标签: java multithreading

我想知道是否有权通过让线程不同时启动来避免线程死锁?是否有其他方法可以避免以下代码中的死锁?

提前致谢!

public class ThreadDeadlocks {

    public static Object Lock1 = new Object();
    public static Object Lock2 = new Object();

    public static void main(String args[]) {

        ThreadDemo1 t1 = new ThreadDemo1();
        ThreadDemo2 t2 = new ThreadDemo2();

        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        t2.start();
    }

    private static class ThreadDemo1 extends Thread {
        public void run() {
            synchronized (Lock1) {
                System.out.println("Thread 1: Holding lock 1...");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                }
                System.out.println("Thread 1: Waiting for lock 2...");
                synchronized (Lock2) {
                    System.out.println("Thread 1: Holding lock 1 & 2...");
                }
            }
        }
    }

    private static class ThreadDemo2 extends Thread {
        public void run() {
            synchronized (Lock2) {
                System.out.println("Thread 2: Holding lock 2...");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                }
                System.out.println("Thread 2: Waiting for lock 1...");
                synchronized (Lock1) {
                    System.out.println("Thread 2: Holding lock 1 & 2...");
                }
            }
        }
    }
}

4 个答案:

答案 0 :(得分:2)

有两种方法可以解决僵局:

  1. 锁定升级。例如,一个持有可共享读取的线程 lock尝试升级到独占写锁。如果不止一个 持有读锁的线程试图升级到写锁,a 死锁结果。这不适用于您正在做的事情。 (另外,我甚至不知道是否可能升级Java中的锁。)
  2. 未指定的锁定顺序。如果线程A锁定对象1,则尝试锁定对象2,而线程B锁定对象2然后尝试锁定对象1,可能导致死锁。这正是你正在做的事情。
  3. 这是解决僵局的唯一方法。每个死锁场景都会归结为其中一个。

    如果您不想要死锁,请不要执行其中任何一种操作。永远不要升级锁,并始终指定锁定顺序。

    这是防止死锁的唯一方法。通过延迟事物来调整线程时间并不能保证工作。

答案 1 :(得分:1)

正如另一个提到的,延迟不会有所帮助,因为线程本质上具有未知的开始时间。当你在一个线程上调用start()时,它会变为可运行,但是你无法知道它何时会运行。

答案 2 :(得分:1)

我假设这只是演示代码,所以你已经知道玩睡眠并不能保证工作(正如其他答案所强调的那样)。

在您的演示代码中,我看到两个尝试避免死锁的选项:

  • 删除线程执行的函数体内的所有睡眠,并在两个线程的开始之间放置一个足够长的睡眠;实际上,这应该给第一个线程有足够的时间进行调度并完成其工作,然后第二个线程将获得两个锁而不会发生争用。但是,您已经知道,调度策略不在您的控制之下,并且根本无法保证。

  • 在两个线程中获取相同顺序的锁,完全不使用任何睡眠,即

     synchronized (Lock1) {
        synchronized (Lock2) {
           // ...
        }
     }
    

    这可以保证消除任何可能的死锁,因为获取Lock1的第一个线程将获得完成其工作的可能性,同时阻止另​​一个线程直到完成。

更新:

要理解为什么以相同的顺序获取锁是避免死锁的唯一保证方法,你应该回想一下锁的整个目的是什么。

一个线程被认为在获得锁定和释放锁定之间拥有锁定。只要一个线程拥有一个锁,没有其他线程可以获得相同的锁。实际上,另一个线程在尝试获取相同的锁时会阻塞。

Java中的每个对象都有一个与之关联的内部锁。 synchronized语句允许您自动获取指定对象的内部锁,并在代码执行后将其释放。

答案 3 :(得分:0)

不,在不同时间启动线程不是避免死锁的方法 - 实际上,您在不同的启动时间尝试的是启发式序列化其关键部分。 ++看看为什么在这个答案

[用解决方案编辑]

  

还有其他方法可以避免以下代码中的死锁吗?

最简单的方法是在两个线程上以相同的顺序获取锁

synchronized(Lock1) {
   // do some work
  synchronized(Lock2) {
     // do some other work and commit (make changes visible)
  }
}

如果您的代码逻辑要求您不能这样做,那么请使用java.util.concurrent.locks类。例如

ReentrantLock Lock1=new ReentrantLock();
ReentrantLock Lock2=new ReentrantLock();

private static class ThreadDemo1 extends Thread {
    public void run() {
         while(true) {
            Lock1.lock(); // will block until available
            System.out.println("Thread 1: Holding lock 1...");
            try {
                // Do some preliminary work here, but do not "commit" yet
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            System.out.println("Thread 1: Waiting for lock 2...");
            if(!Lock2.tryLock(30, TimeUnit.MILLISECOND)) {
               System.out.println("Thread 1: not getting a hold on lock 2...");

               // altruistic behaviour: if I can't do it, let others 
               // do their work with Lock1, I'll try later
               System.out.println("Thread 1: release lock 1 and wait a bit");
               Lock1.unlock();
               Thread.sleep(30);
               System.out.println("Thread 1: Discarding the work done before, will retry getting lock 1");

            }
            else {
               System.out.println("Thread 1: got a hold on lock 2...");
               break;
            }
        }
        // if we got here, we know we are holding both locks
        System.out.println("Thread 1: both locks available, complete the work");
        // work...
        Lock2.unlock(); // release the locks in the reverse... 
        Lock1.unlock(); // ... order of acquisition

    }
}

// do the same for the second thread

++为了说明为什么在不同时间启动线程的延迟不是一个万无一失的解决方案,请考虑在下面的示例中是否可以将其中一个线程延迟10秒。然后想想如果你真的不知道等待多长时间你会做什么

private static class ThreadDemo1 extends Thread {
    public void run() {
        synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            try {
                // modelling a workload here: 
                // can take anywhere up to 10 seconds
                Thread.sleep((long)(Math.random()*10000));
            } catch (InterruptedException e) {
            }
            System.out.println("Thread 1: Waiting for lock 2...");
            synchronized (Lock2) {
                System.out.println("Thread 1: Holding lock 1 & 2...");
            }
        }
    }
}

private static class ThreadDemo2 extends Thread {
    public void run() {
        synchronized (Lock2) {
            System.out.println("Thread 2: Holding lock 2...");
            try {
                // modelling a workload here: 
                // can take anywhere up to 10 seconds
                Thread.sleep((long)(Math.random()*10000));
            } catch (InterruptedException e) {
            }
            System.out.println("Thread 2: Waiting for lock 1...");
            synchronized (Lock1) {
                System.out.println("Thread 2: Holding lock 1 & 2...");
            }
        }
    }
}