假设有一个共享资源库 ResourcePool
,它内部维护了两类资源:ResourceTypeA
和 ResourceTypeB
。现在有两个线程 Thread1
和 Thread2
,它们都需要从资源库中分别获取一种资源才能继续执行。Thread1
需要 ResourceTypeA
而 Thread2
需要 ResourceTypeB
。资源库提供了等待和通知机制,允许线程在资源不可用时等待,并在资源变为可用时得到通知。
初始条件
ResourcePool
中ResourceTypeA
和ResourceTypeB
都不可用。Thread1
首先到达,开始等待ResourceTypeA
。Thread2
随后到达,开始等待ResourceTypeB
。
死锁发生过程
-
资源释放与等待
ResourceTypeA
变为可用,此时ResourcePool
应该通知等待的线程。ResourcePool
使用notify()
方法尝试唤醒一个等待的线程。
-
错误的线程被唤醒
- 假设
notify()
方法随机唤醒了Thread2
而不是Thread1
。 Thread2
尝试获取ResourceTypeB
,但是它仍然不可用,因此Thread2
无法继续执行,它再次进入等待状态。
- 假设
-
正确的线程未被唤醒
Thread1
,它实际上可以使用现在可用的ResourceTypeA
,却没有被唤醒,因此它继续等待。
-
没有更多的通知
- 由于
ResourcePool
只调用了一次notify()
,并且没有新的资源变为可用来触发额外的通知,Thread1
将永远等待下去,即使它需要的资源已经可用。
- 由于
-
死锁发生
Thread1
和Thread2
都在等待,无法继续执行,也无法唤醒对方,系统进入死锁状态。
死锁的解决
- 使用
notifyAll()
替代notify()
,这样在ResourceTypeA
变为可用时,所有等待的线程都会被唤醒。Thread1
和Thread2
都有机会检查它们等待的资源是否可用,并相应地继续执行或者继续等待。 - 设计资源获取逻辑时使用循环来检查条件,并在条件不满足时继续等待(超时等待)。这样即使被错误唤醒,线程也会再次检查资源是否满足其需求,并在不满足时重新等待,而不是假设资源已经可用并进入死锁状态。
通过这种方式,可以确保每个线程在其所需资源变为可用时能够被唤醒并继续执行,从而避免了死锁的发生。