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

3

7

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

Atalaya answered 20/10, 2015 at 19:10 Comment(7)
Yes, theoretically the new thread could notify() before the main thread calls wait().Leanto
Don't think of a synchronized block as preventing a race. It merely limits the scope of the race. It's like a section of an automobile race course where the track is too narrow cars to overtake one another. They're still racing, but they have to go through that one section single-file. Same goes for the synchronized block: The threads aren't racing in here, but they race to get here, and they race everywhere else.Jabe
@jameslarge that was an amazing analogy! No two threads can be in the synchronized block simultaneously, but unless explicitly specified there's no telling who could get there first, correct?Atalaya
(almost) always do wait() in a loop.Sender
It is recommended that applications not use wait, notify, or notifyAll on Thread instancesSender
Why? Because of the risk? @bayou.ioAtalaya
I'm just now seeing Nathan's comment regarding this, sorry. @bayou.ioAtalaya
O
5

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.

Ogdoad answered 20/10, 2015 at 19:38 Comment(8)
remove notify() from ThreadB, and wait() still wakes up... :DSender
@bayou.io: yes, because the thread sends a notification when it ends.Ogdoad
I was looking at this again, theoretically couldn't b notify before main thread ever waits? I know the odds are slim because of concurrency, but I was curious if I was thinking about this wrong @NathanHughesAtalaya
@trevalexandro: you have to allow for that possibility. as a practical matter starting a thread takes a while, the scheduler may favor letting the current thread run instead of switching over right away. but it's up to the scheduler.Ogdoad
What's the efficiency of putting something like Thread.sleep(2000) in ThreadB's synchronized block? That way if it's reached first, it'll be put on hold which will give the main thread a chance to wait? @NathanHughesAtalaya
@trevalexandro: what's the point? The main thread will usually get a chance to wait anyway.Ogdoad
I mean aside from the boolean check, just wondering about different solutions. @NathanHughesAtalaya
@trevalexandro: that doesn't seem like a good solution because it relies on an assumption about when the other thread will get to execute.Ogdoad
S
2

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.
Scoliosis answered 20/10, 2015 at 19:21 Comment(0)
C
2

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.

Counterintelligence answered 20/10, 2015 at 19:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.