要了解线程死锁, 首先要明白什么是死锁
死锁
通俗点讲: 死锁就是两个或两个以上的进程或线程在执行过程中, 由于竞争资源或者由于彼此通信而造成的一种阻塞的现象, 若无外力作用, 它们都将无法推进下去
用简单一点的例子来说吧
比如这个交通堵塞的例子, 从图中可以看到四个方向行驶的汽车互相阻塞, 如果没有任何一个方向的汽车退回去, 那么将形成一个死锁
上述图中有产生死锁的四个原因:
1. 互斥条件: 一个资源每次只能被一个线程使用图上每条路上只能让一个方向的汽车通过, 故满足产生死锁的条件之一
2. 请求与保持条件: 一个进程因请求资源而阻塞时, 对已获得的资源保持不放可以看出, 图上每个方向的汽车都在等待其他方向的汽车撤走, 故满足产生死锁的条件之二
3. 不剥夺条件: 进程已获得的资源, 在未使用完之前, 不能强行剥夺这里假设没有交警, 那么没有人能强行要求其他方向上的汽车撤离, 故满足产生死锁的条件之三
4. 循环等待条件: 若干进程或线程之间形成一种头尾相接的循环等待资源关系这个在图中很直观地表达出来了
死锁 Java 代码小例子
- [html] view plain copy
- print?
- package huaxin2016_9_9;
- public class ThreadDeadlock {
- public static void main(String[] args) throws InterruptedException {
- Object obj1 = new Object();
- Object obj2 = new Object();
- Object obj3 = new Object();
- // 新建三个线程
- Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
- Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
- Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
- // 让线程依次开始
- t1.start();
- // 让线程休眠
- Thread.sleep(5000);
- t2.start();
- Thread.sleep(5000);
- t3.start();
- }
- }
- class SyncThread implements Runnable{
- private Object obj1;
- private Object obj2;
- // 构造函数
- public SyncThread(Object o1, Object o2){
- this.obj1=o1;
- this.obj2=o2;
- }
- @Override
- public void run() {
- // 获取并当前运行线程的名称
- String name = Thread.currentThread().getName();
- System.out.println(name + "acquiring lock on"+obj1);
- synchronized (obj1) {
- System.out.println(name + "acquired lock on"+obj1);
- work();
- System.out.println(name + "acquiring lock on"+obj2);
- synchronized (obj2) {
- System.out.println(name + "acquired lock on"+obj2);
- work();
- }
- System.out.println(name + "released lock on"+obj2);
- }
- System.out.println(name + "released lock on"+obj1);
- System.out.println(name + "finished execution.");
- }
- private void work() {
- try {
- Thread.sleep(30000);
- }
- catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
上述死锁小例子运行结果为
- [html] view plain copy
- print?
- t1 acquiring lock on java.lang.Object@675d5ed4
- t1 acquired lock on java.lang.Object@675d5ed4
- t2 acquiring lock on java.lang.Object@7943f708
- t2 acquired lock on java.lang.Object@7943f708
- t3 acquiring lock on java.lang.Object@46767615
- t3 acquired lock on java.lang.Object@46767615
- t1 acquiring lock on java.lang.Object@7943f708
- t2 acquiring lock on java.lang.Object@46767615
- t3 acquiring lock on java.lang.Object@675d5ed4
可以很直观地看到, t1t2t3 都在要求资源, 却都保持自己的资源, 故而引起死锁
解决方案:
1. 打破互斥条件, 我们需要允许进程同时访问某些资源, 这种方法受制于实际场景, 不太容易实现条件;
2. 打破不可抢占条件, 这样需要允许进程强行从占有者那里夺取某些资源, 或者简单一点理解, 占有资源的进程不能再申请占有其他资源, 必须释放手上的资源之后才能发起申请, 这个其实也很难找到适用场景;
3. 进程在运行前申请得到所有的资源, 否则该进程不能进入准备执行状态这个方法看似有点用处, 但是它的缺点是可能导致资源利用率和进程并发性降低
4. 避免出现资源申请环路, 即对资源事先分类编号, 按号分配这种方式可以有效提高资源的利用率和系统吞吐量, 但是增加了系统开销, 增大了进程对资源的占用时间
(1). 最简单最常用的方法就是进行系统的重新启动, 不过这种方法代价很大, 它意味着在这之前所有的进程已经完成的计算工作都将付之东流, 包括参与死锁的那些进程, 以及未参与死锁的进程;
(2). 撤消进程, 剥夺资源终止参与死锁的进程, 收回它们占有的资源, 从而解除死锁这时又分两种情况: 一次性撤消参与死锁的全部进程, 剥夺全部资源; 或者逐步撤消参与死锁的进程, 逐步收回死锁进程占有的资源一般来说, 选择逐步撤消的进程时要按照一定的原则进行, 目的是撤消那些代价最小的进程, 比如按进程的优先级确定进程的代价; 考虑进程运行时的代价和与此进程相关的外部作业的代价等因素;
(3). 进程回退策略, 即让参与死锁的进程回退到没有发生死锁前某一点处, 并由此点处继续执行, 以求再次执行时不再发生死锁虽然这是个较理想的办法, 但是操作起来系统开销极大, 要有堆栈这样的机构记录进程的每一步变化, 以便今后的回退, 有时这是无法做到的
来源: http://www.92to.com/bangong/2018/03-06/33417213.html