Reputation: 137
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
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:
running
flag.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:
As far as possible use higher-level concurrency functionality provided by the java.util.concurrent.* classes.
Avoid using bare threads, shared variables, mutexes and so on. (They are difficult to use correctly.)
If / when you do need to use the low-level primitives:
Upvotes: 1
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
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
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
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