Java线程无法启动(或需要很长时间才能启动)

时间:2017-03-20 07:16:31

标签: java multithreading spring-boot

我有一个简单的(或者至少我认为是)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,但同样缺乏成功。

第二次编辑:是的,我在这里编写示例代码时犯了一些错误。固定的。

5 个答案:

答案 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.waitObject.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

的更多信息here

答案 4 :(得分:1)

我认为这是一个内存模型/同步问题。您有一个线程写入running标志,另一个线程读取它而不进行任何同步。这可能导致读取(在本例中为main)线程没有看到写入(在本例中为子)线程所做的running更新。

解决方案:

  1. 使用synchronized方法读取和更新running标志。
  2. running声明为volatile
  3.   

    最令人沮丧的是它有时会起作用(通常是我第一次在重建后上传/运行它)。并且从未在我的电脑上工作过。

    众所周知,涉及不正确同步的错误很难追查。问题的根源在于,在现代多核处理器上,您的代码可以正常工作......取决于是否/何时刷新内存缓存。这取决于诸如您拥有多少物理内核,系统有多忙,(OS提供的)线程安排的行为。当您使用调试器或添加跟踪图时,责任可以改变您尝试追踪的行为。

    最佳策略如下:

    1. 尽可能使用java.util.concurrent。*类提供的更高级别的并发功能。

    2. 避免使用裸线程,共享变量,互斥锁等。 (它们很难正确使用。)

    3. 如果/当您确实需要使用低级原语时:

      • 确保您真正了解Java内存模型和
      • 非常仔细地分析您的代码,以确保它没有经典的竞争条件和内存访问危险。