Threads – Termination

Threads were briefly introduced in the post Threads in Java. Here, we continue the conversation by introducing a way to terminate threads once one in a set reaches a prescribed condition.

Thread termination example

The example below is a simple guessing game.  A value between 1 and 1 billion is chosen (500 million in this case – line 119) and three threads (line 117) simultaneously “guess” based on their prescribed interval (lines 100-105). Once one correctly guesses, the method stopThreads(threadName) (lines 69-73) is called, which sets the winner and interrupts all threads. Interruption alone is not sufficient to terminate the process. To do so gracefully, running threads must check their interruption status (Thread.currentThread().isInterrupted() – line 100) and terminate their work (in this case, exit the while loop).

import static java.lang.System.out;
import java.util.ArrayList;
import java.util.List;
 
/**
 * This example illustrates one way to terminate threads. Here, a simple 
 * game of guess the value is levied against a series of threads that 
 * try to brute fore the answer. If one locates the value, all threads are
 * terminated and the winner identified.
 * @author Ray Hylock
 */
public class ThreadsTermination {
    // global variables
    private List<Thread> tList;
    private String threadName;
     
    /**
     * Creates, executes, and waits for threads to finish. If the value
     * is guessed, then the winner is identified.
     * @param threads   the number of threads
     * @param N         the number to inclusively search over
     * @param V         the value to find
     * @throws InterruptedException 
     */
    private void execute(final int threads, final long N, final long V) 
            throws InterruptedException{
        // create the threads
        tList = new ArrayList<Thread>();
        for(int i=0; i<threads; i++)
            tList.add(new Thread(new CompThread(interval(N, i+1, threads), V),
                    "thread " + i));
         
        // start threads - done separately to exclude instantiation if timing
        threadName = null;
        out.println("Searching for value: " + V);
        for(Thread t : tList) t.start();    // calls run() in CompThread
         
        // wait for them to finish
        for(Thread t : tList) t.join();
         
        // print the winner if one exists
        if(threadName != null) out.println("Winner: " + threadName);
        else out.println("The value was not identified");
    }
     
    /**
     * Compute {@code thread}'s range within {@code N}. This is useful when
     * segmenting a task into non-intersecting portions.
     * @param N         the value to segment
     * @param thread    the current thread number
     * @param nthreads  the number of threads
     * @return          a 2D array where the start (index 0) and end (index 1) 
     *                  values are placed
     */
    private long[] interval(final long N, final int thread, final int nthreads){
        long interval[] = new long[2];
        interval[0] = (thread == 1) ? 0 : (N/nthreads)*(thread-1);
        interval[1] = (thread == nthreads) ? N-1 : (N/nthreads)*thread - 1;
        return interval;
    }
     
    /**
     * Sets the winning thread and interrupts those currently executing.
     * The threads will have to check the interrupted status 
     * {@link Thread#currentThread()#isInterrupted()} while iterating to
     * terminate gracefully.
     * @param threadName winning thread name
     */
    private synchronized void stopThreads(String threadName){
        this.threadName = threadName;
        for(int i=0; i<tList.size(); i++)
            if(tList.get(i).isAlive()) tList.get(i).interrupt();
    }
     
    /**
     * The computational thread. Implements {@link Runnable}, so {@code run()} 
     * is executed when {@code start()} is invoked on an instantiated
     * {@link CompThread} object.
     */
    private class CompThread implements Runnable {
        private long start, end;    // range to compute
        private long V;
         
        /**
         * Create a new computational thread.
         * @param interval  the start-to-end range
         * @param V         the value to find
         */
        public CompThread (final long interval[], final long V){
            // convert 0-index to 1-index (e.g., sum 1...100 instead of 0...99)
            start = interval[0] + 1;
            end = interval[1] + 1;
            this.V = V;
        }
         
        @Override
        public void run(){
            boolean fnd = false;
            // while still in range and not interrupted
            while(start <= end && !Thread.currentThread().isInterrupted()){
                if(start++ == V) {
                    fnd = true;
                    break;
                }
            }
            // if found, terminate process and claim prize
            if(fnd) stopThreads(Thread.currentThread().getName());
        }
    }
     
    /**
     * Main method.
     * @param args  command line arguments
     * @throws InterruptedException 
     */
    public static void main(String args[]) throws InterruptedException{
        int threads = 3;
        long N = 1_000_000_000;
        long V = N/2;
        ThreadsTermination t = new ThreadsTermination();
        t.execute(threads, N, V);
    }
}

Output for the example:

Searching for value: 500000000
Winner: thread 1