我正在研究java线程和死锁,我理解死锁的例子,但我想知道是否有一般规则要遵循以防止它。
我的问题是,是否有规则或提示可以应用于java中的源代码以防止死锁?如果是,您能解释一下如何实施吗?
答案 0 :(得分:32)
我的一些快速提示
答案 1 :(得分:15)
封装,封装,封装!你可以用锁做出的最危险的错误就是把你的锁暴露给世界(让它公开)。没有人知道如果你这样做会发生什么,因为任何人都可以在没有对象知道的情况下获得锁定(这也是你不应该锁定this
的原因)。如果您将锁保密,那么您可以完全控制,这使其更易于管理。
答案 2 :(得分:11)
ConcurrentLinkedQueue
代替同步ArrayList
)答案 3 :(得分:8)
答案 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线程转储,我们可以使用 VisualVM 探查器或生成线程转储。 jstack 实用程序。
要分析死锁,我们需要寻找状态为 BLOCKED 的线程,然后寻找等待锁定的资源。每个资源都有一个唯一的ID,通过它我们可以找到哪个线程已经对该对象进行了锁定。
这些是一些指南,通过这些指南,我们可以避免大多数僵局。
答案 9 :(得分:1)
避免嵌套锁。这是死锁的最常见原因。如果你已经拥有一个资源,请避免锁定另一个资源。如果你只使用一个对象锁,几乎不可能出现死锁。
仅锁定所需内容。就像锁定特定的对象字段而不是锁定整个对象(如果它符合您的目的)。
不要无限期等待。
答案 10 :(得分:1)
synchronized
块或Lock。synchronized
个代码块,请确保按特定顺序获取/释放锁定。Lock
API 相关的SE问题:
Avoid synchronized(this) in Java?
答案 11 :(得分:1)
答案 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
结果是两个线程正确完成,避免了任何死锁。