[英]Java Puzzler: busy wait threads stop working
这是某种Java拼图游戏,我偶然发现,无法真正解释。 也许有人可以吗?
短时间后,以下程序挂起。 有时在2个输出之后,有时在80个输出之后,但几乎总是在正确终止之前。 如果第一次没有发生,您可能必须运行几次。
public class Main {
public static void main(String[] args) {
final WorkerThread[] threads = new WorkerThread[]{ new WorkerThread("Ping!"), new WorkerThread("Pong!") };
threads[0].start();
threads[1].start();
Runnable work = new Runnable() {
private int counter = 0;
public void run() {
System.out.println(counter + " : " + Thread.currentThread().getName());
threads[counter++ % 2].setWork(this);
if (counter == 100) {
System.exit(1);
}
}
};
work.run();
}
}
class WorkerThread extends Thread {
private Runnable workToDo;
public WorkerThread(String name) {
super(name);
}
@Override
public void run() {
while (true){
if (workToDo != null) {
workToDo.run();
workToDo = null;
}
}
}
public void setWork(Runnable newWork) {
this.workToDo = newWork;
}
}
现在,很明显,繁忙的等待循环通常不是一个好主意。 但这不是改善,而是了解正在发生的事情。
由于在synchronized
WorkerThread.setWork()
或将WorkerThread.workToDo
字段设置为volatile
时,一切都能按预期工作, WorkerThread.setWork()
我怀疑存在内存问题。
但是为什么会这样呢? 调试无济于事,一旦您开始逐步执行,一切都会按预期进行。
一个解释将不胜感激。
第一个问题是您要从main
线程设置Runnable workToDo
,然后在不同步的情况下在2个分支线程中读取它。 每当您在多个线程中修改一个字段时,都应将其标记为volatile
或有人synchronized
。
private volatile Runnable workToDo;
另外,由于多个线程正在执行counter++
,所以这也需要synchronized
。 我为此推荐一个AtomicInteger
。
private AtomicInteger counter = new AtomicInteger(0); ... threads[counter.incrementAndGet() % 2].setWork(this);
但是我认为真正的问题可能是比赛条件之一。 两个线程都可以将workToDo
设置为Runnable
,然后让它们都返回并将其设置为null
以便它们永远旋转。 我不确定该如何解决。
1. threads[0] has it's `workToDo` set to the runnable. It calls `run()`. 2. at the same time threads[1] also calls `run()`. 3. threads[0] sets the `workToDo` on itself and threads[1] to be the runnable. 4. at the same time threads[1] does the same thing. 5. threads[0] returns from the `run()` method and sets `workToDo` to be `null`. 6. threads[1] returns from the `run()` method and sets `workToDo` to be `null`. 7. They spin forever...
而且,正如您提到的那样,旋转循环很疯狂,但是我认为这是一个演示线程程序。
这些行之间直接发生问题:
workToDo.run();
workToDo = null;
假设发生以下事件序列:
- Original Runnable runs. "Ping!".setWork() called
- Ping! thread realizes workToDo != null, calls run(), the stops between those two lines
- "Pong!".setWork() called
- Pong! thread realizes workToDo != null, calls run()
- "Ping!".setWork() called
- Ping! thread resumes, sets workToDo = null, ignorantly discarding the new value
- Both threads now have workToDo = null, and the counter is frozen at 2,...,80
Program hangs
我的2美分...
import java.util.concurrent.atomic.AtomicReference;
class WorkerThread extends Thread {
private AtomicReference<Runnable> work;
public WorkerThread(String name) {
super(name);
work = new AtomicReference<Runnable>();
}
@Override
public void run() {
while (true){
Runnable workToDo = work.getAndSet(null);
if ( workToDo != null ) {
workToDo.run();
}
}
}
public void setWork(Runnable newWork) {
this.work.set(newWork);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.