bobsyouruncle
bobsyouruncle

Reputation: 137

Java Thread not starting (or taking a long time to start)

I've got a simple (or at least I thought it was) java application that does some work in a Thread.

Something like:

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() above is called in response to a REST request (this is a Spring Boot application). It runs fine on my dev machine (Windows 10, Oracle JDK), and on an Amazon EC2 instance (Amazon Linux, OpenJDK), but not on a Google Compute instance (Ubuntu 16.04, OpenJDK). In this latter case, the worker thread either never starts (task.running is never set to true), or it sometimes starts after 60+ seconds. I am bewildered.

Given the task itself is not very complex (plus the fact that setting the "running" flag is the first thing it does, and this never happens) leads me to think this is some weird JVM/system-related issue, but I really have no clue.

The most frustrating thing is it works sometimes (usually the first time I upload/run it after a rebuild). And has never failed to work on my PC.

edit: I have tried using the Oracle JRE in Ubuntu, but with the same lack of success.

2nd edit: Yep, I made some mistakes writing the sample code here. Fixed.

Upvotes: 1

Views: 3415

Answers (5)

Stephen C
Stephen C

Reputation: 719679

I think this is a memory model / synchronization problem. You have one thread writing the running flag, and another one reading it without doing any synchronization. This can lead to the reading (in this case main) thread not seeing the updates to running made by the writing (in this case child) thread.

Solutions:

  1. Use synchronized methods to read and update the running flag.
  2. Declare running as volatile.

The most frustrating thing is it works sometimes (usually the first time I upload/run it after a rebuild). And has never failed to work on my PC.

Bugs involving improper synchronization are are notoriously difficult to track down. The root of the problem is that on modern multi-core processors, your code works ... or not ... depending on whether / when memory caches get flushed. This can depend on things like how many physical cores you have, how busy your system is, the behavior of the (OS provided) thread schedule. And when you use a debugger or add traceprints, that is liable to alter the behavior that you are trying to track down.

The best strategy is as follows:

  1. As far as possible use higher-level concurrency functionality provided by the java.util.concurrent.* classes.

  2. Avoid using bare threads, shared variables, mutexes and so on. (They are difficult to use correctly.)

  3. If / when you do need to use the low-level primitives:

    • make sure that you really understand the Java Memory Model, and
    • analyse your code very carefully to make sure that it is free of classical race conditions, and memory access hazards.

Upvotes: 1

Vasu
Vasu

Reputation: 22452

You also need to mark your running variable as volatile as shown below, otherwise, the other threads will not be guaranteed to see the changes to the running variable.

private volatile boolean running;

Changes to a volatile variable are always visible to other threads

You can look here more about volatile

Upvotes: 1

Ferrybig
Ferrybig

Reputation: 18834

You code isn't thread safe, because you access a single variable from multiple threads, because you never used any safe access structure to access this variable, it means that the internal java code optimizer may optimalise your while loop as a while(true) loop.

While you can declare your variable volatile to solve the problem, the real solution would be using Object.wait and Object.notifyAll to handle the waiting period.

After your listener has started the main thread, it should enter a while loop inside a synchronized block that check the condition with wait in between, example:

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
}

Then inside your task, you need to set running to true, and then notify all threads.

synchronized (this) {
    this.running = true;
    this.notifyAll();
}

Upvotes: 1

omt66
omt66

Reputation: 5019

Runnable is an interface, so you are creating a new interface called Task! However, you are also providing an implementation for the run() method. This is not going to compile.

Probably, this is what you wanted to do:

class Task implements Runnable {
    public boolean running = false;

    @Override
    public void run() {
        running = true;
        // long running processing
    }
}

Another mistake is that you are calling the thread's run() method directly! You should call start() instead.

A sample code might look like following:

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
        }
    }
} 

Upvotes: 3

kropla
kropla

Reputation: 163

This is not a way you should start a thread task in Java. In this case you just calling run method. To run thread you should use start method:

 thread.start();

Upvotes: 2

Related Questions