我试图理解多线程编程的概念。我知道死锁和互斥锁的概念,但我找不到以下问题的答案。使用互斥锁时如何出现死锁问题?
4 回答
这是一个如何在 Java中引发死锁的具体示例。我们产生两个线程。第一个在 上获取互斥锁a
,然后等待一秒钟,然后尝试在 上获取锁b
。第二个获取锁b
,然后等待,然后尝试获取a
。结果是程序进入死锁并永远运行。
public class Deadlock {
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
new Thread() {
@Override
public void run() {
synchronized (a) {
/* wait for a second to make it likely the other thread can acquire b */
try { Thread.sleep(1000); } catch (Exception e) { }
synchronized (b) {
System.out.println("Acquired a, then b.");
}
}
}
}.start();
new Thread() {
@Override
public void run() {
synchronized (b) {
/* wait for a second to make it likely the other thread can acquire a */
try { Thread.sleep(1000); } catch (Exception e) { }
synchronized (a) {
System.out.println("Acquired b, then a.");
}
}
}
}.start();
}
}
请注意,不保证此代码会进入死锁。线程调度程序完全有权在启动第二个线程之前运行第一个线程完成,反之亦然。由于此示例中的大量等待时间,几乎可以肯定系统将进入死锁,但如果不是睡眠一秒钟,而是两个线程在每次获取锁之前都在做一些可变时间长度的计算,则以下任何事情可能随机发生:
- 僵局
"Acquired a, then b."
, 其次是"Acquired b, then a."
"Acquired b, then a."
, 其次是"Acquired a, then b."
你如何防止这种情况发生?
- 尽可能避免完全使用线程。
- 不是在线程之间共享资源,而是让每个线程对自己的数据进行操作,并相互发送不可变的消息。
- 如果您绝对必须使用共享资源,请尽量减少使用的互斥锁的数量。如果一切都在同一个对象上同步,那么只有一个互斥锁,就不会发生死锁。
- 如果您绝对必须拥有大量互斥锁,并且线程获取它们的组合,请执行以下操作:编写一个定义锁的总排序的函数。然后,当您需要执行涉及多个互斥体的操作时,创建所涉及的互斥体列表,对它们进行排序,然后在操作开始时按排序顺序输入它们。
看看java.util.concurrent包。它包含许多好东西,可以照顾许多真正的毛茸茸的部分。
此外,我不能夸大线程在意想不到的地方切换的程度。一个常见的错误是逐行查看每个线程,并想象这些行可以交织的不同方式。但这还不够:一个线程可能会在某个库深处的某个嵌套函数调用的中间被切换出去。
死锁是当一个线程正在锁定资源 A 并等待被第二个线程锁定的资源 B 的情况。同时,第二个线程正在等待被第一个线程锁定的资源 A,该线程正在等待被第二个线程锁定的资源 B,即.... :)
解决方案是通过线程模型的“智能”设计或使用队列解耦线程或使用不可变对象来避免这种情况,从而不需要同步等。
当必须获得多个锁时使用Lock对象以避免锁排序死锁。在这种情况下,Lock 对象中的 tryLock() 方法将立即返回值 false。结果是只有一个线程会同时持有两个锁。在同步代码的情况下,线程将在等待下一个锁时阻塞持有一个对象的锁。请参见下面的示例代码:
while (true) {
if (a.lock.tryLock()) {
try {
if (b.lock.tryLock()) {
try {
// manipulate protected state
} finally {
b.lock.unlock();
}
}
} finally {
a.lock.unlock();
}
}
}
互斥锁是同步块的关键。同步块/互斥变量使用不当会导致死锁。