0

Not sure I understand threads correctly, could someone tell me whether I´m right or wrong in the following example:

class Task {
String taskName;
private Thread thread;
boolean isFinished;

public Task(String name){
    taskName = name;
}

public void createTask(final Runnable r) {
    thread = new Thread(r){
        public void run(){
            if(r != null) {
                r.run();
                isFinished = true;
            }
        }
    };
    thread.start();
}
}

What I'm actually doing in my app is that I set the isFinished to true, and have an observer that whenever the isFinished is true does some stuff. I'm afraid that isFinished is set to true, before all code in the Runnable I pass as parameter is actually terminated.

Isn't the run method suppose to put the code I pass, in a separate thread and run that code asynchronously?

hmatar
  • 2,437
  • 2
  • 17
  • 27
dalvarezmartinez1
  • 1,385
  • 1
  • 17
  • 26
  • 2
    It sounds like you want to block until the `Runnable` finishes. That is what [`join()`](http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()) is for. – unholysampler Jan 11 '13 at 21:11
  • Given how answers are all over the place, it would help if you gave us a bit more information. What behavior are you expecting? What behavior are you actually observing? – LordOfThePigs Jan 11 '13 at 21:22
  • @unholysampler Where do I put the join? – dalvarezmartinez1 Jan 11 '13 at 21:26
  • 1
    dalvarezmartinez1: unholysampler meant that you call join on the started thread object, and then your current thread should block until the started thread finishes. However, this is not 100% reliable, because you don't know whether join completed successfully or not. FutureTask is better :) – lbalazscs Jan 11 '13 at 21:50

7 Answers7

2

Close, but your new thread is already given the runnable object to execute. You really want to give it a wrapper which runs the r.run() method and then sets isFinished.

Change:

public void createTask(final Runnable r) {
    thread = new Thread(r){
        public void run(){
            if(r != null) {
                r.run();
                isFinished = true;
            }
        }
    };
    thread.start();
}

to

public void createTask(final Runnable r) {
    thread = new Thread( new Runnable {
        public void run(){
            if(r != null) {
                r.run();
                isFinished = true;
            }
        }
    });
    thread.start();
}

I would be remiss if I didn't point out the thread-unsafetiness of isFinished. You will not be guaranteed to notice when the thread finishes, without adding synchronization. I recommend you add:

public synchronized boolean getIsFinished()
{
    return isFinished;
}

public synchronized void setIsFinished(boolean finished)
{
    isFinished = finished;
}

And use these methods to get or set the isFinished flag. Given your lack of synchronization here, you may be seeing other thread-safety oddities, depending on whether or not your r.run() method and your other "observer" are sharing data without synchronization as well.

dashrb
  • 952
  • 4
  • 10
  • Note that LordOfThePigs' answer addresses the isFinished synchronization as well, in a shorter mechanism. – dashrb Jan 11 '13 at 21:17
  • thanks for the answer, actually the code I provided APPARENTLY works fine, what I don't understand is why! Because I was expecting the run method to create another thread for running the code, and go to the next operation which would be isFinished = true; for some reason, which would be wrong if the code the run method is suppose to run hasn't finished, not sure I made myself understood. – dalvarezmartinez1 Jan 11 '13 at 21:42
1

You should almost never pass a Runnable into the constructor of a Thread and override the Thread's run() method.

The following two pieces of code are essentially identical:

Runnable r = new Runnable( )
{
    public void run( )
    {
        // do stuff...
    }
};

new Thread( r ).start( );

An here's another way to accomplish the same thing by overriding run():

(new Thread( )
{
    public void run( )
    {
        // do stuff...
    }
}).start( );
ulmangt
  • 5,343
  • 3
  • 23
  • 36
0

No, the run method simply is a normal function, that you can override when extending the Thread class in order to implement your own behaviour.

It's the start method of the Thread class that starts a new thread and runs that code async.

Alexander Kjäll
  • 4,246
  • 3
  • 33
  • 57
0

Well your code is partially right, and partially wrong.

You are correct that isFinished will only be set to true once everything inside the runnable you are passing in the parameter has finished executing.

However, due to the particular semantics of the java memory model (I'll get into more details about that below), it is possible that when you set isFinished to true, that change is only visible to the thread that has set that variable to true. If you want your code to work as expected, you need to declare isFinished as volatile. This will make any changes you make to that variable immediately visible by other threads.

Another way to do it is to declare isFinished as AtomicBoolean rather than boolean. This class has many methods that allow you to check and set the boolean in an atomic way, helping you to avoid many common multithreading pitfalls.

LordOfThePigs
  • 11,050
  • 7
  • 45
  • 69
  • Thanks a lot. This is similar to the accepted answer, not sure which one was first, apologize, if it was yours, also the accepted answer shows that is not normal to pass the Runnable to the constructor and execute it using the reference. – dalvarezmartinez1 Jan 11 '13 at 22:32
0

The way you wrote your code, isFinished will not be set to true until r.run() is complete. It may be appearing otherwise because you may have some data visibility issues due to missing synchronization or missing volatile declarations.

It's a little bit odd since you're both passing in the Runnable to the constructor, but calling it using the reference from your method declaration, not the one inside the thread. But it "works", there's just a redundancy there.

As an aside, don't forget @Override in your anonymous class :)

Affe
  • 47,174
  • 11
  • 83
  • 83
  • Also thanks to @Alexander Kjäll for showing that only the method start in the Thread class, creates a new thread and runs that async, the run method is just a normal method. – dalvarezmartinez1 Jan 11 '13 at 22:29
0

I suggest you use the synchronization primitive specifically designed for your problem.

This primitive is called CountDownLatch.

Here is the updated code:

class Task {

  String taskName;
  private Thread thread;
  CountDownLatch finishedSignal = new CountDownLatch( 1 );

  public Task(String name){
    taskName = name;
  }

  public void createTask(final Runnable r) {
    thread = new Thread(r){
        public void run(){
            if(r != null) {
                r.run();
                finishedSignal.countDown( );
            }
        }
    };
    thread.start();

    finishedSignal.await( );
  }
}
Alexander Pogrebnyak
  • 44,836
  • 10
  • 105
  • 121
0

You should use a FutureTask instead of your own Task class. It has an isDone() method, and it is integrated nicely with the Executor framework.

Most importantly the happens-before relationships are maintained as you expect it (actually in your code the problem is not that isFinished is set to true, before all code in the Runnable is terminated, but the other way: possibly it will not be set to true in the original thread even if the Runnable is terminated)

Example:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("work done");
    }
};

FutureTask<Void> task = new FutureTask<Void>(runnable, null);
ExecutorService es = Executors.newSingleThreadExecutor();
es.submit (task);

while (!task.isDone()) {
    System.out.println("waiting...");
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
lbalazscs
  • 17,474
  • 7
  • 42
  • 50
  • can not change to FutureTask now, the use of Task is quite extended in the application, and it would suppose lot's of changes. Can you explain better this: "but the other way: possibly it will not be set to true in the original thread even if the Runnable is terminated" – dalvarezmartinez1 Jan 11 '13 at 21:51
  • Well your Task could extend FutureTask or delegate to it. About your question: a field written by one thread is not guaranteed to be seen by a different thread. See the first answer to this question: http://stackoverflow.com/questions/461896/what-is-the-most-frequent-concurrency-issue-youve-encountered-in-java – lbalazscs Jan 11 '13 at 21:55