一、死锁案例

Java多线程编程中,为了保证线程安全,用到了锁的概念,当两个线程互相等待对方释放同步的监视器时就会造成死锁。对于死锁,如果没有外力作用,死锁会一直继续下去,程序将无法正常往下执行。现在写一个简单的死锁案例。

先定义两个类,在这两个类中分别定义两个synchronized方法,这就表示如果有线程调用了对象中的方法,线程将持有该对象的锁。

ClassA.java

public class ClassA {

	public synchronized void init(ClassB b) {
		System.out.println("A对象的init方法执行了!");
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("A对象准备调用B对象的end方法!");
		b.end();
	}
	
	public synchronized void end() {
		System.out.println("A对象的end方法执行了!");
	}

}

ClassB.java

public class ClassB {
	
	public synchronized void init(ClassA a) {
		System.out.println("B对象的init方法执行了!");
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("B对象准备调用A对象的end方法!");
		a.end();
	}
	
	public synchronized void end() {
		System.out.println("B对象的end方法执行了!");
	}
}

然后再写一个测试类

TestMain.java

public class TestMain implements Runnable{

	public ClassA a = new ClassA();
	
	public ClassB b = new ClassB();
	
	
	public static void main(String[] args) {
		TestMain main = new TestMain();
		Thread thread = new Thread(main);
		
		thread.start();
		main.runB();
	}

	@Override
	public void run() {		
		a.init(b);
	}
	
	public void runB() {
		b.init(a);
	}

}

运行程序,执行结果如下:

B对象的init方法执行了!
A对象的init方法执行了!
B对象准备调用A对象的end方法!
A对象准备调用B对象的end方法!

程序在输出“A对象准备调用B对象的end方法!”之后就不再往下运行,但是从运行结果来看程序肯定是没有运行结束的,所以程序进入了一个假死的状态。

现在分析一下上面的代码

1、首先在main函数中定义一个子线程,然后调用线程的start()方法启动子线程,并且在main函数所在的线程(暂定叫做主线程)调用runB()方法,这样就有了两个线程在运行了。

2、在主线程的runB()方法中调用了ClassB对象的init方法,因为init方法有synchronized关键字,所以主线程获得ClassB对象的锁。程序首先输出B的init方法执行的内容,然后ClassB调用sleep方法执行休眠,休眠时不释放ClassB的对象锁,主线程让出CPU执行权,这时子线程获得CPU的执行权,子线程开始执行。

3、子线程调用ClassA的init方法,同样的获得了ClassA对象的锁并输出开始执行的内容,接着执行sleep方法开始休眠,子线程释放CPU执行权,但是不释放ClassA的对象锁。

4、这时主线程从休眠中醒来,获得CPU的执行权,准备执行ClassA的end方法,end方法也是被synchronized修饰的,所以主线程想获得ClassA的对象锁,但是因为ClassA的对象锁已被子线程拥有,所以主线程等待子线程释放锁。

5、接着子线程从休眠中醒来,准备执行ClassB的end方法,但是因为主线程拥有ClassB的对象锁,所以子线程也等待主线程释放锁,这就造成了相互等待的状态,程序不能继续往下执行。