关于多线程死锁,以前对这个概念总是很朦胧,不知道具体该怎么理解。
前不久从网上看到一篇文章,感觉写的很透彻,很形象,现在摘录下来以备日后省查,希望也能对其他人起到帮助。
俗话说的好,人多好办事!在程序当中也是这样,如果在同一个应用程序中需要并行处理多件任务,那就可以创建
多条线程。但是人多了,往往也会出现冲突,使得这个工作无法进行下去了,(三个和尚没水喝啊),这就是死锁。
举个形象的例子,就像三个人(A,B,C)在玩三个球(1,2,3),规则很简单,每个人都必须先拿到自己左手边的球,
才能拿自己右边的球,两手都有球之后,才能把球放下。如下图。
这个游戏看起来似乎可以永远进行下去,但是若干局之后,如果三个人刚好都只拿到自己左手边的球,都等着拿右手边的球,
但是因为谁都不能放手,那么这三个人(线程)都将陷入无尽的等待中了,这就是传说中的死锁。
下面就用JAVA来举例,例子中已经创建了三个boolean型的静态变量ball1、ball2、ball3(初始值为false),TURE代表球
被拿起,FALSE代表球仍放在地上,接下来就是三个线程类:
public class test { static class ThreadA extends Thread //A的线程 { @Override public void run() { // TODO Auto-generated method stub //super.run(); while(true)//无限循环 { while(ball3==true)//如果ball3已被拿起,贼进入等待。 {} ball3=true;//当ball3被放下后,立刻拿起 while(ball1==true)//如果ball1已被拿起,则进入等待。 {} ball1=true;//拿起ball1. System.out.println("A已经拿到两球");//输出结果,方便观察死锁状态 ball1=ball3=false;//放下两球 } } } static class ThreadB extends Thread //B的线程 { @Override public void run() { // TODO Auto-generated method stub //super.run(); while(true)//无限循环 { while(ball1==true)//如果ball1已被拿起,贼进入等待。 {} ball1=true;//当ball1被放下后,立刻拿起 while(ball2==true)//如果ball2已被拿起,则进入等待。 {} ball2=true;//拿起ball1. System.out.println("B已经拿到两球");//输出结果,方便观察死锁状态 ball1=ball2=false;//放下两球 } } } static class ThreadC extends Thread //C的线程 { @Override public void run() { // TODO Auto-generated method stub //super.run(); while(true)//无限循环 { while(ball2==true)//如果ball2已被拿起,贼进入等待。 {} ball2=true;//当ball2被放下后,立刻拿起 while(ball3==true)//如果ball3已被拿起,则进入等待。 {} ball3=true;//拿起ball3. System.out.println("C已经拿到两球");//输出结果,方便观察死锁状态 ball2=ball3=false;//放下两球 } } } /** * @param args */ private static boolean ball1,ball2,ball3=false; public static void main(String[] args) { // TODO Auto-generated method stub ThreadA A=new ThreadA(); ThreadB B=new ThreadB(); ThreadC C=new ThreadC(); A.start(); B.start(); C.start(); } }
运行这个程序,你会看到有若干行打印信息后,就不再有输出,那么就说明它“死锁”了。
那么我们如何来消除“死锁”呢?首先,让我们来看看产生“死锁”的必要条件:
1. 互斥,就是说多个线程不能同时使用同一资源,比如,当线程A使用该资源时,B线程只能等待A释放后才能使用。
2. 占有等待,就是某线程必须同时拥有N个资源才能完成任务,否则它将占用已经拥有的资源直到拥有他所需的所有资源为止,就好像游戏中,必须两个球都拿到了,才能释放;
3.非剥夺,就是说所有线程的优先级都相同,不能在别的线程没有释放资源的情况下,夺走其已占
有的资源;
4.循环等待,就是没有资源满足的线程无限期的等待。
到了这,有的读者已经明白了,只要打破这几个必要条件,就能打破“死锁”!那么先来看看互斥:
要打破这个条件,就是要让多个线程能共享资源,就相当于A和B能同时举起ball1一样,当然在
这个例子里我们可以这样修改规则,但是在其他程序中就不一定能了,比如说一个“读”线程,一
个“写”线程,他们都能操作同一文件。这种情况下,我们就不能“又读又写”文件,否则有可能会读
到脏数据!因此我们很少从这方面考虑。
占有等待:
打破占有等待,只要当检测到自己所需的资源仍被别的线程占用,即释放自己已占有的资源(毫不利己,专门利人,呵呵~),或者在经过一段时间的等待后,还未得到所需资源,才释放,这就能打破占有等待。我们可以把While(true)中的代码改一下(以A为例):
Outer:While(true)
{
int i=0;
while(ball3==true){}//如果ball3已被拿起,则进入等待
ball3=true;//当ball3被放下后,立刻拿起。
while(ball1==true)
{
i++;
if(i==1000)//当计数达到1000后还未得到ball1,则放下ball3,并重新开始。
{
ball3=false;
break Outer;
}
ball1=true;//拿起ball1
System.out.println(“A已经拿到两球!”)//为了方便观察死锁现象。
ball1=ball3=false;//然后放下两球
}
}
其他两个线程也是如此,即可打破占有等待;
非剥夺:
打破非剥夺,只要给线程指定一个优先级即可。比如例子中,我们设优先级从高到低为A、B、C
,即当A需要ball3,而C正占有它,但是A的优先级比C高,那么C必须马上释放ball3.同理,A对B、B对C也是如此。代码修改如下:
public class test { static class ThreadA extends Thread //A的线程,优先级最高。 { @Override public void run() { // TODO Auto-generated method stub //super.run(); while(true)//无限循环 { while(ball3==true)//如果ball3已被C拿起,贼进入等待。 {
ball3=false;//强迫C放下ball3
} ball3=true;//当ball3被放下后,立刻拿起 while(ball1==true)//如果ball1已被B拿起,则进入等待。 {ball1=false;}//强迫B放下ball1 ball1=true;//拿起ball1. System.out.println("A已经拿到两球");//输出结果,方便观察死锁状态 ball1=ball3=false;//放下两球 } } } static class ThreadB extends Thread //B的线程,优先级第二 { @Override public void run() { // TODO Auto-generated method stub //super.run(); while(true)//无限循环 { while(ball1==true)//如果ball1已被A拿起,贼进入等待。 {} ball1=true;//当ball1被放下后,立刻拿起 while(ball2==true)//如果ball2已被C拿起,则进入等待。 {ball2=false;}//强迫C放下ball2 ball2=true;//拿起ball1. System.out.println("B已经拿到两球");//输出结果,方便观察死锁状态 ball1=ball2=false;//放下两球 } } } static class ThreadC extends Thread //C的线程,优先级最低 { @Override public void run() { // TODO Auto-generated method stub //super.run(); while(true)//无限循环 { while(ball2==true)//如果ball2已被拿起,贼进入等待。 {} ball2=true;//当ball2被放下后,立刻拿起 while(ball3==true)//如果ball3已被拿起,则进入等待。 {} ball3=true;//拿起ball3. System.out.println("C已经拿到两球");//输出结果,方便观察死锁状态 ball2=ball3=false;//放下两球 } } } /** * @param args */ private static boolean ball1,ball2,ball3=false; public static void main(String[] args) { // TODO Auto-generated method stub ThreadA A=new ThreadA(); ThreadB B=new ThreadB(); ThreadC C=new ThreadC(); A.start(); B.start(); C.start(); } }
通过这样的修改我们就能打破“非剥夺”(唉~和这个社会一样,可怜的小C啊!)。
最后的循环等待的解决方法其实和占有等待是一样的,都是等待一段时间后释放资源,好了,希望
能通过这个例子让读者对“死锁”有一定的认识。