死锁概述
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁发生须具备的条件
- 互斥条件:进程间的锁具有排他性。
- 请求和保持条件:一个进程已经持有一个资源,但仍然提出对另一个资源的请求。
- 不剥夺条件:一个进程所持有的资源不能在自己使用完之前被系统剥夺,需自己释放。
- 环路等待条件:由于第2条件,多个进程间的请求形成环路。
演示例子
1 | 4j |
运行结果
其中出现了死锁,需要手动停止任务的执行,此时任务结束状态为:Process finished with exit code -1
例子说明
一个简单的死锁类:
当DeadLock类的对象flag == 1
时(td1),先锁定o1,睡眠500毫秒
而td1在睡眠的时候另一个flag == 0
的对象(td2)线程启动,先锁定o2,睡眠500毫秒
td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;
td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;
td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
- 构造了两个死锁实例;该实例实现了Runnable接口。
- 分别声明两个线程,分别在两个状态时请求另一个线程中的对象。
- 其中锁定线程休眠500毫秒:可以为另一个线程提供充足的时间来对该线程中的对象进行请求。
- 当在main函数中创建两个死锁实例td1,td2,并给定flag状态后:td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。(对应于前后例子运行结果中flag-0或1的顺序)
- 死锁的实现:使用两层synchronized锁进行对象资源锁定,其中当两个线程分别获得第一层锁,在分别获取两一个线程的第二层锁时出现死锁。
- 当将类中的两个休眠时间都去掉后,出现正常的执行结束:
1
2
3
4
5
609:40:45.688 [Thread-1] INFO com.mmall.concurrency.deadLock.DeadLock - flag:0
09:40:45.688 [Thread-0] INFO com.mmall.concurrency.deadLock.DeadLock - flag:1
09:40:45.696 [Thread-1] INFO com.mmall.concurrency.deadLock.DeadLock - 0
09:40:45.696 [Thread-0] INFO com.mmall.concurrency.deadLock.DeadLock - 1
Process finished with exit code 0
死锁的避免策略
- 调整加锁顺序;避免交叉地对对象加锁,而应按序加锁。
- 加锁时限:将synchronized锁换成类似重入锁Reentrant具有锁定时间的锁机制。
- 死锁检测:是一种较好的死锁预防机制,但是实现较困难。
当为对象加锁不可避免地会出现交叉,且锁超时也不可行时,使用死锁检测机制。
- 每当一个线程获得了锁,会在线程和锁相关的数据结构中将其作为标记记下来;每当有其他线程请求该锁时也要记下相应标记;当一个线程请求锁失败时,该线程应根据锁之间的关系判断是否有死锁发生,并响应出对应的处理。
死锁发生时,线程该做哪些事?
- 当死锁发生时,释放所有的锁,并回退;并等待一个随机的时间后,进行重试(即回退等待)。但重试中仍有可能会发生死锁。
- 上述第一条,在重试中仍发生死锁,还可以提前为线程设定优先级:使回退等待中的一部分(非全部)释放锁并回退,其他的线程继续持有锁。
- 为了避免上述第二条中因线程拥有固定不变的优先级可能导致的线程饥饿等情况,应采取一定的策略,设置随机的(或其他更好的方式)线程优先级。