防止java中死锁的提示

时间:2013-05-27 21:48:16

标签: java multithreading deadlock

我正在研究java线程和死锁,我理解死锁的例子,但我想知道是否有一般规则要遵循以防止它。

我的问题是,是否有规则或提示可以应用于java中的源代码以防止死锁?如果是,您能解释一下如何实施吗?

13 个答案:

答案 0 :(得分:32)

我的一些快速提示

  • 不要使用多个线程(例如,通过强制要求所有内容都在EDT中完成,就像Swing一样)
  • 不要同时持有几个锁。如果这样做,请始终以相同的顺序获取锁
  • 在持有锁时不执行外国代码
  • 使用可中断的锁

答案 1 :(得分:15)

封装,封装,封装!你可以用锁做出的最危险的错误就是把你的锁暴露给世界(让它公开)。没有人知道如果你这样做会发生什么,因为任何人都可以在没有对象知道的情况下获得锁定(这也是你不应该锁定this的原因)。如果您将锁保密,那么您可以完全控制,这使其更易于管理。

答案 2 :(得分:11)

  1. 使用无锁数据结构避免锁定(例如,使用ConcurrentLinkedQueue代替同步ArrayList
  2. 始终以相同的顺序获取锁定,例如在获取具有较高数值的锁之前,为每个锁分配唯一的数值并获取具有较低数值的锁。
  3. 在超时期限后释放你的锁(从技术上讲,这不会阻止死锁,只是在它们发生后才有助于解决它们)

答案 3 :(得分:8)

  1. 不要使用锁。
  2. 如果必须,请将锁保持在本地。全局锁定可能非常棘手。
  3. 握住锁时尽量少做。
  4. 使用stripes仅锁定数据段
  5. 首选不可变类型。很多时候,这意味着复制数据而不是共享数据。
  6. 请使用比较和设置(CAS)机制,例如,请参阅AtomicReference

答案 4 :(得分:8)

阅读并理解 Java:并发和实践。这不是关于避免死锁的“提示”。我永远不会雇用一个知道一些技巧的开发人员来避免死锁并且经常避免死锁。这是关于理解并发性的。幸运的是,有一本关于该主题的综合中级水平书,所以请阅读它。

答案 5 :(得分:5)

给定设计选择,使用消息传递,其中只有锁定在队列push / pop中。这并不总是可行,但如果是这样,你将会遇到很少的死锁。你仍然可以得到它们,但你必须非常努力:)

答案 6 :(得分:4)

在防止死锁方面,几乎只有一条重要规则:

  

如果您需要在代码中使用多个锁,请确保每个人始终以相同的顺序获取它们。

保持代码免受锁定应该几乎总是你的目标。您可以尝试通过使用不可变或线程局部对象和无锁数据结构来摆脱它们。

答案 7 :(得分:3)

这是死锁的典型例子:

public void methodA()
{
  //...

  synchronized(lockA)
  {
     //...
     synchronized(lockB)
     {
      //...
     }
   }
}


public void methodB()
{
  //...

  synchronized(lockB)
  {
     //...
     synchronized(lockA)
     {
      //...
     }
   }
}

如果被许多线程调用,这种方法可能会造成很大的死锁。这是因为对象以不同顺序锁定。这是死锁的最常见原因之一,因此如果您想避免它们,请确保按顺序获取

答案 8 :(得分:2)

Java中的死锁是一种编程情况,其中两个或更多线程被永久阻止。至少有两个线程和两个或更多资源会导致Java死锁。

如何在Java中检测死锁

要检测Java中的死锁,我们需要查看应用程序的 java线程转储,我们可以使用 VisualVM 探查器或生成线程转储。 jstack 实用程序。

要分析死锁,我们需要寻找状态为 BLOCKED 的线程,然后寻找等待锁定的资源。每个资源都有一个唯一的ID,通过它我们可以找到哪个线程已经对该对象进行了锁定。

如何避免Java死锁

这些是一些指南,通过这些指南,我们可以避免大多数僵局。

  • 通过打破循环等待条件来避免死锁:为此,您可以在代码中进行安排以对获取和释放锁施加顺序。如果将以一致的顺序获取锁并以相反的顺序释放锁,则不会出现一个线程持有由另一线程获取的锁的情况,反之亦然。
  • 避免嵌套锁::这是造成死锁的最常见原因,如果已经持有另一个资源,请避免锁定另一个资源。如果仅使用一个对象锁,几乎不可能出现死锁情况。
  • 仅锁定要求的内容:您应该仅对必须处理的资源获得锁定,如果我们仅对其中一个字段感兴趣,那么我们应该仅锁定该特定字段不完整的对象
  • 避免无限期等待::如果两个线程正在使用线程联接无限期地等待彼此完成,则可能会出现死锁。如果您的线程必须等待另一个线程结束,那么总是最好使用join,并在最长的时间内等待线程结束。

答案 9 :(得分:1)

  1. 避免嵌套锁。这是死锁的最常见原因。如果你已经拥有一个资源,请避免锁定另一个资源。如果你只使用一个对象锁,几乎不可能出现死锁。

  2. 仅锁定所需内容。就像锁定特定的对象字段而不是锁定整个对象(如果它符合您的目的)。

  3. 不要无限期等待。

答案 10 :(得分:1)

  1. 除非必要,否则不要通过多个线程共享数据。如果在创建/初始化后无法更改数据,请坚持使用最终变量。
  2. 如果您无法避免多个线程之间的共享数据,请使用精细的synchronized块或Lock
  3. 如果您只使用synchronized个代码块,请确保按特定顺序获取/释放锁定。
  4. 请注意其他替代方案:volatile或AtomicXXX变量或Lock API
  5. 相关的SE问题:

    Avoid synchronized(this) in Java?

    Difference between volatile and synchronized in Java

    Volatile boolean vs AtomicBoolean

答案 11 :(得分:1)

  • 避免嵌套锁定
  • 避免不必要的锁定
  • 使用thread join()

答案 12 :(得分:0)

我喜欢这个例子。它启动两个共享布尔标志的线程:

public class UntilYouUpdateIt 
{
    public static boolean flag = true;

    public static void main(String[] args) throws InterruptedException 
    {
        Thread t1 = new Thread(()->
        {
            while(flag){}
            System.out.println("end");
        });
        t1.start();

        Thread.sleep(100);

        Thread t2 = new Thread(()->
        {
           flag = false;
           System.out.println("changed");
        });
        t2.start();
      }
}

第一个线程将一直循环直到flag为假,这在第二个线程的第一行中发生。该程序永远不会完成,其输出结果将是:

changed

第二个线程死亡,而第一个线程将永远循环。

为什么会发生? Compiler opmitizations。 Thread1将不再检查标志的值,如:

  • 循环内的操作很便宜(无所事事)
  • 编译器知道没有其他实体可以修改标志值(因为第一个线程没有,第二个线程已经死亡)。因此,假定标记始终为真

换句话说,线程1将始终从 flag 读取cache值,该值设置为true


解决/测试此问题的两种方法:

    Thread t1 = new Thread(()->
    {
        while(flag)
        {
           System.out.print("I'm loopinnnng");
        }
        System.out.println("end");
    });

如果包含了一些“繁重的”操作(int i=1或类似的方法都不起作用),例如System调用,则优化器会更加谨慎,检查flag布尔值以了解他是否在浪费资源。输出为:

I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
(....)
changed
end

I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
(....)
end
changed

取决于最后为哪个线程分配了cpu时间。

在使用布尔变量时,避免此类死锁的正确解决方案应包括 volatile 关键字。

volatile告诉编译器:在涉及此变量时不要尝试优化。

因此,此相同代码仅添加了关键字:

public class UntilYouUpdateIt 
{
    public static volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException 
    {
        Thread t1 = new Thread(()->
        {
            while(flag){}
            System.out.println("end");
        });
        t1.start();

        Thread.sleep(100);

        Thread t2 = new Thread(()->
        {
           flag = false;
           System.out.println("changed");
        });
        t2.start();
      }
}

将输出:

changed
end

end
changed

结果是两个线程正确完成,避免了任何死锁。