Java多线程学习之路(四)—死锁(DeadLock)
1.定义
死锁就是多个线程在竞争共享资源的时候,相互阻塞,不能脱身的状态(个人理解).其实死锁一定程度上可以看成一个死循环.
举个现实生活中的例子,一个单方向的狭小的车道上,两个方向同时来了两批车流,结果在路中间相遇了,双方想要过去都必须等待对方先过去.最后两批车流只能在路中间干耗着.
在多线程编程中,如果Thread1拥有Resource1的锁,同时申请Resource2的锁,而与此同时,Thread2拥有Resource2的锁,同时申请Resource1的锁.双方都只能等待对方执行完之后释放锁才能继续执行.最终导致死锁.
2 死锁产生的必要条件(注意不是充要条件)
- 互斥(mutual exclusion):至少一个资源处于非共享模式,即一个资源一次只能一个线程使用
- 占有并等待(hold and wait):一个线程在申请一个共享资源的时候,应该至少占有一个共享资源,且不会释放
- 非抢占(no preemption):共享资源不能被抢占,除非当前线程完成任务后释放其对象锁
- 循环等待(circular wait):占有与申请产生了一个循环
强调:满足上述四个条件也不一定会产生死锁,他们只是必要条件
3.如何解决死锁
- 预防死锁:使得死锁根本不会产生.
- 避免死锁:阻止会产生死锁的操作.
- 允许死锁产生,破坏并恢复死锁.
- 忽略死锁(也称鸵鸟算法) 干脆不管它.
事实上,大部分的OS(Windows,Linux等)都选择第四条,即忽略死锁.将问题抛给应用开发人员.所以Windows出现问题,第一件事就是重启(狗头).
4.预防死锁
预防死锁其实非常简单,那就是破坏四个必要条件中的任意一个就可以了.
- 破坏互斥:将资源设置为非互斥的,即多个线程可以同时访问.
- 破坏占有并等待:有两种方式可以破坏 1.每个线程在执行前直接把所有需要的资源全部申请了.这样就不会执行到一半再去申请别的资源了,但是这样要求它在开始执行前就知道自己需要哪些资源. 2.每个线程在申请一个资源的时候都需要确保自己本身并没有占有其他资源,如果占有那就必须先释放已占有的资源.但是这要也可能有问题,那就也许它同时需要两个资源才能执行.
- 破坏非抢占:有两种方式:1.如果一个已经占有了一定资源的线程申请其他资源失败,该线程原来所占有的资源就变得可以被抢占了(相当于被隐形释放了),只有当该线程获得原有资源和申请的新资源的时候才能重新运行. 2.如果一个线程Thread1正在申请一个正在被别的线程Thread2占有的资源,OS可以可以强制要求Thread2释放Thread1所需要的资源.(前提是Thread1的线程优先级更高)
- 破坏循环等待:对所有资源类型进行完全排序,而且要求所有线程按照资源顺序进行申请,比如可以看到上文死锁的例子,如果对资源排序,并按顺序申请资源,那么Thread1和Thread2都需要Resource1和Resource2,但是只能按照先申请Resource1再申请Resource2的顺序,这样的话就不会出现这样的问题