16.5.6 死锁
16.5.6 死锁
什么时候会发生死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁,
发生死锁会怎样
Java
虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
程序 死锁示例
死锁是很容易发生的,尤其在系统中出现多个同步监视器的情况下,如下程序将会出现死锁。
1 | package thread; |
运行上面程序,将会看到如下效果,此时程序既无法向下执行,也不会抛出任何异常,就一直”僵持”着。
1 | 主线程 进入了A实例的firstA()方法 睡眠.zzz |
究其原因,是因为上面程序中A对象和B对象的方法都是同步方法,也就是A对象和B对象都是同步锁。
程序中两个线程执行:
- 一个线程的线程执行体是
DeadLock
类的run()
方法, - 另一个线程的线程执行体是
DeadLock
的init()
方法(主线程调用了init()
方法)。
其中run()
方法中让B对象调用firstB()
方法,
而init()
方法让A对象调用firstA()
方法。
发生死锁的过程分析
- 从本次运行结果来看,主线程的
init()
方法先执行,调用了A对象的firstA
方法,进入firstA
方法之前,该线程会对A对象加锁,不过当程序执行到firstA
方法中的①号代码时,主线程睡眠200毫秒,在睡眠期间,主线程继续持有A对象的锁。 - 这时候
CPU
切换到执行另一个线程(副线程),所以看到副线程开始执行B实例的firstB
方法,进入firstB
方法之前,该线程会对B对象加锁,不过当程序执行到firstB
方法中的②号代码时,副线程也睡眠200毫秒,在睡眠期间,副线程继续持有B对象的锁。 - 接下来主线程会先醒过来,继续向下执行,执行到③号代码处时,要调用B对象的
lastB()
方法,但执行该方法之前必须先对B对象加锁,由于此时副线程正保持着B对象的锁,所以主线程无法加锁,主线程阻塞; - 接下来副线程醒过来,继续向下执行,执行到④号代码处时,要调用A对象的
lastA()
方法,但执行该方法之前必须先对A对象加锁,由于主线程还没有释放A对象的锁,副线程无法加锁,副线程也阻塞。 - 这就出现了主线程保持着A对象的锁,等待对B对象加锁,而副线程保持着B对象的锁,等待对A对象加锁,两个线程互相等待对方先释放,所以就出现了死锁。
由于Thread
类的suspend
方法也很容易导致死锁,所以Java
不再推荐使用该方法来暂停线程的执行
如何写一个死锁
- 先要有两个线程,设为主线程,副线程
- 要有两个类,设为A类对象和B类对象
- A类对象有两个同步方法,
- A类对象的第一个方法(设置
firstA
)的参数是B类对象,- 执行该方法时,先睡眠一会,然后调用B类对象的第二个无参同步方法
lastB
- 执行该方法时,先睡眠一会,然后调用B类对象的第二个无参同步方法
- A类对象的第二个同步方法
lastB
是个无参方法。
- A类对象的第一个方法(设置
- B类对象有两个同步方法,
- B类对象的第一个方法
firstB
的参数传入A类对象,
-firstB
方法中调用A类对象的第二个无参同步方法lastA
- B类对象的第二个方法同样是无参方法。
- B类对象的第一个方法
- A类对象有两个同步方法,
- 创建A类对象,创建B类对象.
- 主线程的执行体中调用A类对象的第一个方法
firstA
,该方法传入B类对象作为参数. - 副线程的执行题中条用B类对象的第一个方法
firstB
,该方法传入A类对象作为参数. - 启动这两个线程,就会发生死锁.