2012.12.25

Wait until any thread terminates

In Java programming, it’s quite common to start a pool of threads and wait for all threads to terminate: this can be achieved in several ways, the most simple of which is the one of “joining” all the threads.

However, there are situations in which you may want to wait for any thread to terminate, i.e. stop waiting when at least one thread terminates — and abort all the others.
You may want to have such a behaviour when you have several threads working on the same problem (for example, a search problem) and you want to stop all threads when at least one of them finds a solution.

I bet many of you already how to implement a solution for this problem, or can easily figure out by playing with wait()/notify() or CountDownLatch.

However, I've coded a handy library (Race Runner) that can save you time and hassle.

This is how it works:

  1. Your Runnable objects have to implement the RaceRunnable interface:
    public interface RaceRunnable extends Runnable {
        public void stop();
        public void setRaceListener(RaceListener raceListener);
        // inherits run() from Runnable
    }
    
    In the implementation of the stop() method, you should do something to stop the run() method of the RaceRunnable: for example, you can can set a flag (e.g. stopped = true) that will break some loop.
    In the implementation of the setRaceListener(RaceListener raceListener) method, just retain a reference to the RaceListener being passed as a parameter: I will explain you how to use this object.
  2. Implement the run() method of RaceRunnable so that it stops looping when the stop() method is called, and calls raceListener.win(this) when it finds a solution for the problem (i.e. “wins” the race). Here is the skeleton of a typical implementation:
    class YourRaceRunnable implements RaceRunnable {
    
        private volatile boolean stopped;
        private RaceListener raceListener;
    
        public YourRaceRunnable() {
            // Constructor
        }
    
        @Override
        public void run() {
            while (!stopped) {
                // search for a solution...
                if (solutionFound) {
                    raceListener.win(this);
                }
            }
        }
    
        @Override
        public void stop() {
            stopped = true;
        }
    
        @Override
        public void setRaceListener(RaceListener raceListener) {
            this.raceListener = raceListener;
        }
    
    }
    
  3. Create all your RaceRunnables and put them in a Collection. Pass this collection to the constructor of the Runner class, the one that actually manages the logic of the “race”, hiding you the details.
    Collection<RaceRunnable> raceRunnables = ...
    // Instantiate and populate the "raceRunnables" collection
    Runner runner = new Runner(raceRunnables, 300); // timeout: 5 minutes
    
    The second argument of the constructor is the timeout, i.e. the maximum number of seconds to wait for any RaceRunnable to call raceListener.win().
  4. Call the run() method of the Runner you have just instantiated. It will terminate when RaceListener.win() is called, the timeout expires, or all the RaceRunnables terminate without any of them calling RaceListener.win() (i.e. “nobody wins”).
    You can figure out which RaceRunnable won the race by calling the Runner.getWinner() method, and extract the solution of the problem directly from it.
    runner.run();
    RaceRunnable winner = runner.getWinner();
    if (winner != null) {
        // Cast winner to its concrete implementation and
        // extract the solution from it
    }
    
    When getWinner() returns null, it means that either the timeout expired, or all the RaceRunnables terminated without any of them having called RaceListener.win().
    You can distinguish between these two cases by calling the Runner.isTimeoutExpired() method, which returns true if the timeout has expired.

If you find all of this a little bit confusing and cumbersome, please take 30 seconds to see a couple of working examples you can find in the library distribution, which also includes the Javadoc.

Tags: java
comments powered by Disqus
Fork me on GitHub