死锁,本质上是一个哲学问题。
当一个线程先占有了同步类(synchronized)的资源A,同时接着要求同步类的资源B。而恰恰在这中间,另一个线程捷足先登,占有了同步类的资源B,同时接着要求资源A。而此时资源A已经被第一个线程占有了。于是,这两个线程的代码都执行不下去了,它们都在等对方释放资源,好继续往下执行,这时候,程序就形成了“死锁”的状态。
所谓同步类的资源,就是被synchronized关键字修饰的资源,一般是语句块。这种资源,是哪个线程先执行,哪个线程先独自占有,直到执行完所有的代码,才释放给其它的线程使用。
下面写一个死锁的例子程序:
/*假设一个人需要拿到两把钥匙才能完成一件任务。
* 第一个人先开始执行任务,拿到了keyOne,准备拿keyTwo的时候,第二个人也开始了任务,并抢先拿到了keyTwo。
* 这时候,第1个人需要拿到keyTwo才能完成任务,第2个人需要拿到keyOne才能完成任务。但是钥匙在对方手里,谁也玩不成任务
* 这样,问题就无解了,形成了僵局
*/
public class TestDeadLock extends Thread{ //线程类TestDeadLock
static String keyOne="keyone"; //第一把钥匙
static String keyTwo="keytwo"; //第二把钥匙
static boolean flag = true; //设置旗标,规定先进来的线程,先去拿第一把钥匙;后进来的线程,先去拿第2把钥匙
public void run(){ //覆盖run方法
if(flag){ //先进来的人先去运行keyOne
flag = false; //设置旗标,以维持运行规则
synchronized(keyOne){ //拿第一把钥匙
System.out.println("I have "+keyOne+". I need "+keyTwo);
try{
Thread.sleep(100); //让第一个线程睡100毫秒,好让第二个线程执行
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(keyTwo){ //第一个线程睡完100毫秒之后,接着拿第二把钥匙,但是在它睡着的时候,第二把钥匙被第二个线程拿走了
System.out.println("I have "+keyTwo);
}
}
}
else //后进来的线程,执行这一段
{
flag = true; //设置旗标
synchronized(keyTwo){ //去拿第二把钥匙
System.out.println("I have "+keyTwo+". I need "+keyOne);
try{
Thread.sleep(100); //第二个线程也睡100毫秒,好让第一个线程继续执行
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(keyOne){ //第二个线程睡晚100毫秒之后,接着去拿第一把钥匙,但是第1把钥匙早就被第一个线程拿走了,只能等待
System.out.println("I have "+keyTwo);
}
}
}
}
public static void main(String[] args){
//分别启动两个线程
new TestDeadLock().start();
new TestDeadLock().start();
}
}
执行结果如下:
可以发现,两个线程分别执行到了一半,然后都在等待对方释放资源,对方释放了资源,才能继续玩下去。
如果要避免这种情况的发生,就要在设计思路上更改。比如两个线程要执行,必须先得到第一把钥匙,才能去拿第二把钥匙,这样在规则上避免了相互抢夺资源的状况。