欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java深入学习之死锁

程序员文章站 2022-07-15 13:59:29
...
    最近在研究Java并发,学习死锁时偶然发现了一种嵌套管程锁死,所以自己实现了下,可能在不小心中就会犯这种错误。

1、死锁实现
    死锁原理很简单,就是线程1先获取锁A,在获取锁B;而线程2先获取锁B,在获取锁A,由于两个线程获取顺序不一样,都没有将各自的锁释放,所以就出现了死锁。代码实现也很简单:
public class DeathLock implements Runnable{
	
	private boolean flag = true;
	
	private Object o1 = new Object();
	
	private Object o2 = new Object();

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		if (flag) {
        System.out.println(Thread.currentThread().getName()+ " 输入为:" + flag);
			flag = false;
			synchronized (o1) {
				try {
					Thread.sleep(2000);
					synchronized (o2) {
						System.out.println(Thread.currentThread().getName()+ " 我是真的");
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		} else {
			System.out.println(Thread.currentThread().getName()+ " 输入为:" + flag);
			synchronized (o2) {
				try {
					Thread.sleep(1000);
					synchronized (o1) {
						System.out.println(Thread.currentThread().getName()+ " 我是假的");
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) {
		
		DeathLock lock1 = new DeathLock();
		Thread th1 = new Thread(lock1);
		Thread th2 = new Thread(lock1);
		th1.start();
		th2.start();
	}
}

简单运行下,
Thread-0 输入为:true
Thread-1 输入为:false
然后都停留在获取第二个锁的阶段,从而照成死锁,解决死锁也很简单,要么保持获取锁的顺序一致,要么就是保证获取锁时,没有其他线程占有锁,或者用JDK1.5后提供的Lock实现都可以,比如tryLock等方法,获取之前先判断下。

当然还有就是将对象锁Object用static修饰,测试的时候无论new多少个实例,都会造成死锁。

2、嵌套管程锁死
前几天再并发编程网上发现,然后自己实现了一下,这种情况不容易制造,也比较难于重现。其照成的原因是:线程1获取了A和B锁,然后释放B锁,等待线程2发过来的信号,然后释放A锁;线程2必须同时获取A锁和B锁,才能向线程1发送信号,这样就会照成锁死。代码实现也不复杂,就在上述代码中添加一个lock和unlock方法,run方法进行修改下:

	public void lock() {
		synchronized (o1) {
			while (!flag) {
				try {
					Thread.sleep(200);
					synchronized (o2) {
						o2.wait();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			flag = false;
		}
	}
	
	public void unlock() {
		synchronized (o1) {
			try {
				Thread.sleep(200);
				flag = true;
				synchronized (o2) {
					o2.notify();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void run(){
		lock();
		try {
			System.out.println(Thread.currentThread().getName() + " : 我获得了锁");
		} finally {
			System.out.println(Thread.currentThread().getName() + " : 我将要释放锁");
			unlock();
			System.out.println(Thread.currentThread().getName() + " : 我释放了锁");
		}
	}

这是一段简单的lock实现,原理类似java新增concurrent包的Lock类
执行结果为:
Thread-0 : 我获得了锁
Thread-0 : 我将要释放锁
由此可以看出,线程Thread-0 在执行unlock时就锁死了,因为lock的o1锁没有进行释放,而unlock又需要获取o1锁,那么就照成死锁了。
当然,由于测试时都是用的同一个实例,没有释放当然或照成锁死了。确实,如果是对两个不同的实例进行测试,就不会出现这种情形,当然也就不会有线程安全问题了。
总之,不管什么形式的死锁,造成的原因就是就是因为同一个锁没有被正确的释放时,其他线程希望获取改锁,当然,在JDK1.5之后,提供的Lock类就提供了避免这种情况的方式,使用起来非常方便。