线程死锁

时间:2010-02-17 18:49:15

标签: java multithreading deadlock

我有2个帖子。一个线程打印奇数,第二个线程打印偶数。现在,我必须交替执行线程,以便我可以输出1,2,3,4,5,6,.....

我为此编写了一个程序,这导致了死锁。有人可以解释代码的问题是什么以及如何纠正它?

class BooleanObject {
boolean flag;
BooleanObject(boolean flag) {
    this.flag = flag;
}
}
class EvenThread extends Thread {
Object lock;
BooleanObject flagObj;
EvenThread(Object o, BooleanObject flag) {
    lock = o;
    this.flagObj = flag;
}
public void run() {
    for (int i=2;i<100;i+=2) {
        synchronized(lock) {
            if (flagObj.flag == false) {
                flagObj.flag = true;
                lock.notify();
            }
            else {
                try {
                    while (flagObj.flag == true) {
                        lock.wait();
                    }
                }
                catch (InterruptedException e) {

                }
            }
            System.out.println(i);
        }
    }
}
}

class OddThread extends Thread {
Object lock;
BooleanObject flagObj;
OddThread(Object o, BooleanObject flag) {
    lock = o;
    this.flagObj = flag;
}
public void run() {
    for (int i=1;i<100;i+=2) {
        synchronized(lock) {
            if (flagObj.flag == true) {
                flagObj.flag = false;
                lock.notify();
            }

            else {
                try {
                    while(flagObj.flag == false) {
                        lock.wait();
                    }
                }
                catch (InterruptedException e) {

                }
            }
            System.out.println(i);
        }
    }
}
}

public class EvenOddThreads {
public static void main(String[] args) {
    Object obj = new Object();
    BooleanObject flagObj = new BooleanObject(true);
    EvenThread et = new EvenThread(obj,flagObj);
    OddThread ot = new OddThread(obj,flagObj);

    et.setName("even thread");
    ot.setName("odd thread");

    et.start();
    ot.start();
}
}

3 个答案:

答案 0 :(得分:9)

问题在于自动装箱。当您将flag从true更改为false或反之时,您实际上会获得一个全新的Boolean对象。也就是说,这一行:

flag = false;

相当于:

flag = new Boolean(false);

一旦发生这种情况,你的两个线程就会引用两个不同的Boolean对象,因此它们的标志最终会不同步,并且两个线程都不能发出信号通知另一个线程唤醒。当OddThread更改时,标记EvenThread仍然具有旧标记对象,因此它不会看到新值。

因为Boolean对象是不可变的,所以您需要更改标志以使用其他可变对象,这些对象可以在不创建新对象的情况下更改值。那,或者两个类都引用一个共同的(也许是全局的)变量。

正如@erickson建议你可以使用可变的AtomicBoolean。另一种愚蠢的方法是将flag更改为:

boolean[] flag = new boolean[1];

然后在每个地方使用flag[0]。然后,两个线程都可以更改flag[0],同时始终引用相同的boolean[]数组对象。你不会有自动拳击问题。

...

此外,最好在循环中包含对wait()的任何调用。即使没有人实际调用wait()notify()也会受到虚假唤醒的影响。为了解决这个问题,您应该在醒来后检查您的防护状况,以确保唤醒不是虚假的。

while (flag == true) {
    lock.wait();
}

更新

  

我已根据您的建议进行了更改;但我没有得到预期的输出。我将粘贴上面修改过的代码。这是我得到的输出1 2 4 3 5 6 8 7 9 10 11 13 12 15 17 14 ....

当你最后等待时,一旦你被唤醒,你不要切换flag并通知另一个线程。我建议重新组织你的代码,使它看起来像“等待;打印;通知”。类似的东西:

synchronized (lock) {
    while (flagObj.flag == false) {
        lock.wait();
    }

    System.out.println(i);

    flagObj.flag = false;
    lock.notify();
}

答案 1 :(得分:4)

问题不在于自动装箱。即使始终使用boolean原语,也会发生同样的事情。

这是一个范围问题。每个线程实例都有自己的flag成员,它们完全不同。在一个线程中分配新值时,另一个线程无法看到它。

要使其按预期工作,请创建可变 boolean包装器(AtomicBoolean将完成此任务,但您不会使用其并发属性在此应用程序中),并将该包装器传递给每个线程。每个线程都会改变该单个对象,而不是将新对象分配给它自己的变量。

答案 2 :(得分:0)

你实际上有两个问题。

1)第一个就是这个

if (flag == true) {
    flag = false;
    lock.notify();
}

您将标志引用传递给构造函数,但随后您更改了每个线程的本地flag,这不会影响其他线程的值。

尝试类似

的内容
class Monitor
{
    public static volatile boolean flag;
}

然后在每个帖子中使用Monitor.flag

2)第二个问题(一旦修复了第一个问题),每个线程都需要有这个

synchronized(lock)
{
    lock.notify();
}

在循环结束后,因为否则一个线程将等待()但另一个线程已经完成。