【发布时间】:2013-05-22 18:26:27
【问题描述】:
我正在研究java线程和死锁,我了解死锁的例子,但我想知道是否有一般规则可以防止它。
我的问题是是否有规则或提示可以应用于java中的源代码以防止死锁?如果是,您能解释一下如何实现它吗?
【问题讨论】:
标签: java multithreading deadlock
我正在研究java线程和死锁,我了解死锁的例子,但我想知道是否有一般规则可以防止它。
我的问题是是否有规则或提示可以应用于java中的源代码以防止死锁?如果是,您能解释一下如何实现它吗?
【问题讨论】:
标签: java multithreading deadlock
一些小窍门
【讨论】:
封装、封装、封装! 使用锁可能会犯的最危险的错误就是将您的锁公开(公开)。不知道如果你这样做会发生什么,因为任何人都可以在对象不知道的情况下获得锁(这也是你不应该锁定this 的原因)。如果您将锁保密,那么您将拥有完全的控制权,这使得它更易于管理。
【讨论】:
ConcurrentLinkedQueue 而不是同步的 ArrayList)【讨论】:
offer 和poll 队列,而无需同步访问它
阅读并理解Java:并发与实践。这不是关于避免死锁的“技巧”。我永远不会聘请知道一些避免死锁的技巧并且经常避免死锁的开发人员。这是关于理解并发性。幸运的是,有一本关于该主题的全面的中级书籍,所以去阅读吧。
【讨论】:
【讨论】:
在防止死锁方面几乎只有一条大规则:
如果您需要在代码中拥有多个锁,请确保每个人始终以相同的顺序获取它们。
不过,让您的代码免于锁定应该始终是您的目标。您可以尝试通过使用不可变或线程本地对象和无锁数据结构来摆脱它们。
【讨论】:
给定一个设计选择,在队列推送/弹出中只有锁的情况下使用消息传递。这并不总是可能的,但如果是这样,您将很少遇到死锁。你仍然可以得到它们,但你必须非常努力:)
【讨论】:
我喜欢这个例子。它启动两个共享布尔标志的线程:
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 将永远不会再次检查标志的值,如:
换句话说,Thread1 将始终从 cache 中读取 flag 值,该值设置为 true。
解决/测试此问题的两种方法:
Thread t1 = new Thread(()->
{
while(flag)
{
System.out.print("I'm loopinnnng");
}
System.out.println("end");
});
如果包含一些“繁重”操作(int i=1 或类似的操作都不起作用),例如 System 调用,优化器会更加小心,检查flag boolean 以了解他是否没有浪费资源。输出将是:
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
结果是两个线程都正确完成,避免了任何死锁。
这是一个基本的:
public void methodA()
{
//...
synchronized(lockA)
{
//...
synchronized(lockB)
{
//...
}
}
}
public void methodB()
{
//...
synchronized(lockB)
{
//...
synchronized(lockA)
{
//...
}
}
}
如果被许多线程调用,这种方法可能会造成很大的死锁。这是因为对象以不同的顺序锁定。这是死锁最常见的原因之一,因此如果要避免死锁,请确保按顺序获取锁。
【讨论】:
Java 中的死锁是一种编程情况,其中两个或多个线程被永久阻塞。至少有两个线程和两个或更多资源时会出现 Java 死锁情况。
要检测Java中的死锁,我们需要查看应用程序的java线程转储,我们可以使用VisualVM分析器或使用生成线程转储jstack 实用程序。
为了分析死锁,我们需要查看状态为BLOCKED的线程,然后是等待锁定的资源。每个资源都有一个唯一的 ID,使用它我们可以找到哪个线程已经持有对象的锁。
这些是我们可以避免大多数死锁情况的一些准则。
【讨论】:
避免嵌套锁。这是死锁最常见的原因。如果您已经持有另一个资源,请避免锁定另一个资源。如果您只使用一个对象锁,则几乎不可能出现死锁。
仅锁定需要的内容。如果它符合您的目的,就像锁定对象的特定字段而不是锁定整个对象。
不要无限期地等待。
【讨论】:
synchronized 块或Locks。synchronized 代码块,请确保按特定顺序获取/释放锁。Lock API 相关的 SE 问题:
Avoid synchronized(this) in Java?
【讨论】:
【讨论】:
不幸的是,存在跨多个线程的循环数据流 (A->B->C->A) 的数据模型。上面说的都不管用。 从可能有帮助的角度来看,线程之间的数据传输方法可以使用无锁缓冲输入,如 java 并发集合。数据块本身被实现为无状态的不可变对象。 GUI 系统很可能使用这种方法。这种方式似乎不需要“lockfull”等待通知方案,但是线程并发控制仍然存在,但它隐藏在集合中,并且这种方式是本地的。 这个问题真的很难避免。根源源于数学模型假设的零延迟。但在实际系统中,每个操作都需要时间,因此会产生延迟并最终导致死锁。
【讨论】: