关于多线程死锁,以前对这个概念总是很朦胧,不知道具体该怎么理解。

前不久从网上看到一篇文章,感觉写的很透彻,很形象,现在摘录下来以备日后省查,希望也能对其他人起到帮助。

俗话说的好,人多好办事!在程序当中也是这样,如果在同一个应用程序中需要并行处理多件任务,那就可以创建

多条线程。但是人多了,往往也会出现冲突,使得这个工作无法进行下去了,(三个和尚没水喝啊),这就是死锁。

举个形象的例子,就像三个人(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啊!)。

最后的循环等待的解决方法其实和占有等待是一样的,都是等待一段时间后释放资源,好了,希望

能通过这个例子让读者对“死锁”有一定的认识。