Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a race condition in this example? If so, how could it be avoided?

I'm looking at some notify/wait examples and came across this one. I understand a synchronized block essentially defines a critical section, but doesn't this present a race condition? Nothing specifies which synchronized block is entered first.

public class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();
        b.start();

        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }

        System.out.println("Total is: " + b.total);
        }
    }
}

class ThreadB extends Thread {
    int total;

    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            notify();
        }
    }
}

Output per website:

Waiting for b to complete...

Total is: 4950

like image 263
trevalexandro Avatar asked Oct 20 '15 19:10

trevalexandro


3 Answers

Right, it's not guaranteed which thread will execute first. The thread b could do its notification before the main thread ever starts to wait.

In addition to that, a thread can return from wait without having been notified, so setting a flag and checking it before entering the wait technically isn't good enough. You could rewrite it to something like

public class ThreadA {
    public static void main(String[] args) throws InterruptedException {
        ThreadB b = new ThreadB();
        b.start();

        synchronized(b){
            while (!b.isDone()) {
                System.out.println("Waiting for b to complete...");
                b.wait();
            }
            System.out.println("Total is: " + b.total);
        }
    }
}

class ThreadB extends Thread {
    int total;
    private boolean done = false;

    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            done = true;
            notify();
        }
    }

    public boolean isDone() {return done;}
}

so that the main thread will wait until b is done with its calculation, regardless who starts first.

By the way, the API documentation recommends you not synchronize on threads. The JDK synchronizes on threads to implement Thread#join. A thread that terminates sends a notifyAll that anything joining on it receives. If you were to call notify or notifyAll from a thread you've acquired the lock on, something joining on it could return early. One side effect of this here is that if you remove the notify the code works the same way.

like image 97
Nathan Hughes Avatar answered Oct 25 '22 00:10

Nathan Hughes


Yes, it's a race condition. Nothing prevents ThreadB from starting, entering its run method, and synchronizing on itself prior to ThreadA from entering its synchronized block (thus waiting indefinitely). However, it's very unlikely to ever happen, considering the time it takes for a new thread to begin execution.

The easiest, and most recommended way to handle this type of situation is to not write your own implementation, but opt to use a callable/future provided by an Executor.

To fix this particular case without following standards:

  • Set a boolean 'finished' value set at the end of ThreadB's synchronized block.
  • If the boolean 'finished' is true after entering the synchronized block, then you should not call wait.
like image 28
Wesley Wolfe Avatar answered Oct 24 '22 23:10

Wesley Wolfe


Yes - it is a race as to which thread enters which synchronized block first. For most scenarios of the race, the output and the answer will be the same. For one, however, the program will deadlock:

  1. Main starts calls b.start() and immediately schedules out.
  2. Thread B starts, enters synchronized, calls notify().
  3. Main enters its synchronized block, calls wait()

In this case, main will wait forever since thread b called notify before main blocked on wait().

That said, this is unlikely - but with all threading you should conclude that it will happen and then at the worst possible time.

like image 45
Andy Avatar answered Oct 25 '22 01:10

Andy