我有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();
}
}
答案 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();
}
在循环结束后,因为否则一个线程将等待()但另一个线程已经完成。