Java线程死锁及解决方案

要了解线程死锁,首先要明白什么是死锁

 

死锁

通俗点讲:死锁就是两个或两个以上的进程或线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

 

用简单一点的例子来说吧

 

比如这个交通堵塞的例子,从图中可以看到四个方向行驶的汽车互相阻塞,如果没有任何一个方向的汽车退回去,那么将形成一个死锁

上述图中有产生死锁的四个原因:

1.互斥条件:一个资源每次只能被一个线程使用。图上每条路上只能让一个方向的汽车通过,故满足产生死锁的条件之一

2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。可以看出,图上每个方向的汽车都在等待其他方向的汽车撤走,故满足产生死锁的条件之二

3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。这里假设没有交警,那么没有人能强行要求其他方向上的汽车撤离,故满足产生死锁的条件之三

4.循环等待条件:若干进程或线程之间形成一种头尾相接的循环等待资源关系。这个在图中很直观地表达出来了
 

 

死锁Java代码小例子

 

 

 

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();
		}
	} 
}

上述死锁小例子运行结果为

 

 

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

可以很直观地看到,t1、t2、t3都在要求资源,却都保持自己的资源,故而引起死锁

 

 

解决方案:

1.打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;

2.打破不可抢占条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;

3. 进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低

4.  避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。

 

(1). 最简单、最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程;

(2). 撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素;

(3). 进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值