我有一个简单的(或者至少我认为是)java应用程序,它可以在一个Thread中完成一些工作。
类似的东西:
public class Task implements Runnable {
public boolean running = false;
public void run() {
running = true;
// long running processing
}
}
...
void startTask() {
Task task = new Task();
Thread thread = new Thread(task);
thread.start();
// I added this thinking the calling thread might terminate before
// the new thread starts
while (!task.running) {
try {
Thread.sleep(1);
} catch (InterruptedException ie) {
// log error
}
}
上面的 startTask()
是为了响应REST请求而调用的(这是一个Spring Boot应用程序)。它在我的开发机器(Windows 10,Oracle JDK)和Amazon EC2实例(Amazon Linux,OpenJDK)上运行良好,但在Google Compute实例(Ubuntu 16.04,OpenJDK)上运行不正常。在后一种情况下,工作线程永远不会启动(task.running
永远不会设置为true
),或者有时会在60+秒后启动。我很困惑。
鉴于任务本身并不是非常复杂(加上设置“running”标志是它做的第一件事,而且这种情况从未发生过)这让我觉得这是一些奇怪的JVM /系统相关问题,但是我真的不知道。
最令人沮丧的是它有时(通常是我第一次在重建后上传/运行它)。并且从未在我的电脑上工作过。
编辑:我尝试在Ubuntu中使用Oracle JRE,但同样缺乏成功。
第二次编辑:是的,我在这里编写示例代码时犯了一些错误。固定的。
答案 0 :(得分:3)
Runnable是一个接口,因此您正在创建一个名为Task的新接口!但是,您还提供了run()方法的实现。这不会编译。
可能这就是你想要做的事情:
class Task implements Runnable {
public boolean running = false;
@Override
public void run() {
running = true;
// long running processing
}
}
另一个错误是你直接调用线程的run()方法! 你应该改为调用start()。
示例代码可能如下所示:
void startTask() {
Task task = new Task();
Thread thread = new Thread(task);
thread.start();
// I added this thinking the calling thread might terminate before
// the new thread starts
while (!task.running) {
try {
Thread.sleep(1);
}
catch (InterruptedException ie) {
// log error
}
}
}
答案 1 :(得分:2)
这不是您应该在Java中启动线程任务的方法。在这种情况下,您只需调用run方法。要运行线程,您应该使用start方法:
thread.start();
答案 2 :(得分:1)
您的代码不是线程安全的,因为您从多个线程访问单个变量,因为您从未使用任何安全访问结构来访问此变量,这意味着内部Java代码优化器可以将您的while循环优化为{ {1}}循环。
虽然您可以声明变量while(true)
来解决问题,但真正的解决方案是使用Object.wait
和Object.notifyAll
来处理等待时间。
在你的监听器启动主线程之后,它应该在一个synchronized块中输入一个while循环,检查条件并在其间等待,例如:
volatile
然后在你的任务中,你需要设置运行到thread.start();
// I added this thinking the calling thread might terminate before
// the new thread starts
try {
synchronized (task) {
while (!task.running) {
task.wait();
}
}
} catch (InterruptedException ie) {
// log error
}
,然后通知所有线程。
true
答案 3 :(得分:1)
您还需要将running
变量标记为volatile
,如下所示,否则,其他线程将无法保证看到running
变量的更改。
private volatile boolean running;
对volatile变量的更改始终对其他线程可见
您可以查看有关volatile
答案 4 :(得分:1)
我认为这是一个内存模型/同步问题。您有一个线程写入running
标志,另一个线程读取它而不进行任何同步。这可能导致读取(在本例中为main)线程没有看到写入(在本例中为子)线程所做的running
更新。
解决方案:
running
标志。running
声明为volatile
。最令人沮丧的是它有时会起作用(通常是我第一次在重建后上传/运行它)。并且从未在我的电脑上工作过。
众所周知,涉及不正确同步的错误很难追查。问题的根源在于,在现代多核处理器上,您的代码可以正常工作......取决于是否/何时刷新内存缓存。这取决于诸如您拥有多少物理内核,系统有多忙,(OS提供的)线程安排的行为。当您使用调试器或添加跟踪图时,责任可以改变您尝试追踪的行为。
最佳策略如下:
尽可能使用java.util.concurrent。*类提供的更高级别的并发功能。
避免使用裸线程,共享变量,互斥锁等。 (它们很难正确使用。)
如果/当您确实需要使用低级原语时: