Java Threads of Execution

What is a Thread

A thread of execution (or just thread) is the smallest unit of processing that the operating system (OS) can schedule to be executed on a central processing unit (CPU). Typically a running program is composed of one OS process and a single process can be composed of many threads. You can think of a thread in a computer program as a list of instructions that are executed one after another.

A simple Java program that has no graphical user interface (GUI) is usually a single process with two threads: the main thread which begins executing the code in the main(String[] args) method and a garbage collection thread which periodically runs and frees memory from objects no longer used by the program. A simple Java program that includes a swing GUI is usually a single process with three threads: the main thread, the garbage collection thread, and an event dispatching thread which executes all the code that responds to GUI events such as mouse moves, button clicks, and key presses.

Purpose

Using multiple threads we can divide the work that a program must do into parts that can execute simultaneously on different CPUs or different cores of a CPU. For some programs this reduces the time the program takes to complete its calculations.

Thread Life Cycle

A thread in a Java program has a life cycle as shown in the following UML state diagram.

A UML state diagram showing the life cycle of a Java thread
new state
A thread begins to exist and changes to the new state when it is created using the new keyword.
runnable state
A new thread is separated from its spawning thread when the .start() method is called on it. Then the thread is runnable, alternating between ready and running.
waiting state
A running thread changes to the waiting state when it calls .lock() on a file or other resource and that resource is already locked. When the resource becomes free and the thread obtains the lock, the thread changes to the runnable state again.
timed waiting state
A running thread changes to the timed waiting state when it calls Thread.sleep(millis). It will stay in that state until a number of milliseconds elapses or until it is interrupted. Then it changes back to the runnable state.
terminated state
A thread changes to the terminated state when its .run() method ends.

Two Ways to Create a Thread

There are two ways to create a thread in Java.

«interface»
java.lang.Runnable
+ run() : void
                △
          ┌-----┴---------┐
          ╎
java.lang.Thread
 
+ start() : void
+ run() : void
+ sleep(millis : long) : void




|
MyThread
 
+ run() : void
MyRunnable
 
+ run() : void
  1. Write a class that extends java.lang.Thread and create an object from it. This is demonstrated by the MyThread class on the left hand side of the class diagram and the following code example.
        MyThread mt = new MyThread();
        mt.start();
  2. Write a class that implements java.lang.Runnable, create an object from it, then create a java.lang.Thread object, and pass the new custom runnable object to the new Thread object. This is demonstrated by the MyRunnable class on the right hand side of the class diagram and the following code example.
        MyRunnable mr = new MyRunnable();
        Thread t = new Thread(mr);
        t.start();

Regardless of which method you use to create a thread, you must call the start method on the thread after you create it, and you must write the code you want executed as part of the thread in a method named run(). When this run() method ends, the thread terminates.

Example

The following Java program shows two ways to compute n! (factorial) for large n. The first way is shown in function factorial() at lines 65–80 and doesn’t use threads or in other words is single threaded. The second way is shown in function factorialThreaded() and class FactThread starting at lines 83–127 and 130–155. Notice that class FactThread, which begins at line 130, extends java.lang.Thread. On my computer which has an Intel™ CPU with 4 cores, the multi-thread solution is about four times faster than the single threaded solution for large n because the calculations are spread across four threads each of which executes on a separate core.

Factorial
 
+ main(args : String[]) : void
print(filename : String, n : int, fact : BigInteger, start : long, end : long) : void
factorial(n : int) : BigInteger
factorialThreaded(n : int, nThreads : int) : BigInteger
|
java.lang.Thread
 
+ start() : void
+ run() : void
+ sleep(millis : long) : void
◁────
Factorial.FactThread
# INCREMENT : int
− start : int
− end : int
# product : BigInteger
# done : boolean
+ FactThread(start : int, end : int)
+ run() : void

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigInteger;


public class Factorial {
    public static void main(String[] args) {
        int nTests = 5;
        int n = 9000;
        int nThreads = 4;

        // Compute n! and output the result to factorial.txt.
        System.out.println(n + "! not threaded");
        for (int i = 0;  i < nTests;  ++i) {
            long start = System.currentTimeMillis();
            BigInteger f = factorial(n);
            long end = System.currentTimeMillis();
            print("factorial.txt", n, f, start, end);
        }
        System.out.println();

        // Compute n! using multiple threads and output
        // the result to theaded.txt.
        System.out.println(n + "! threaded");
        for (int i = 0;  i < nTests;  ++i) {
            long start = System.currentTimeMillis();
            BigInteger f = factorialThreaded(n, nThreads);
            long end = System.currentTimeMillis();
            print("threaded.txt", n, f, start, end);
        }
    }


    /** Prints factorial results to the console and a file. */
    private static void print(String filename, int n,
            BigInteger fact, long start, long end) {
        try {
            String s = fact.toString();
            int length = s.length();
            double seconds = (end - start) / 1000.0;

            // Print time and size of result to console.
            System.out.println(seconds + " seconds");
            System.out.println(length + " decimal digits in result");
            System.out.println(n + "! = (see file " + filename + ")");

            // Print time, size, and result to a file.
            PrintStream out = new PrintStream(new File(filename));
            out.println(seconds + " seconds");
            out.println(length + " decimal digits in result");
            out.println(n + "! =");
            for (int chars, i = 0;  i < length;  i += chars) {
                chars = length - i < 80 ? 80 : length - i;
                out.println(s.substring(i, i + chars));
            }
            out.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }


    /** Computes and returns n! */
    private static BigInteger factorial(int n) {
        if (n <= 1) {
            return BigInteger.ONE;
        }
        else {
            /* It is slightly faster to multiply from small numbers
             * to large numbers because smaller numbers stored in a
             * BigInteger require less memory. */
            BigInteger result = BigInteger.valueOf(2);
            for (int i = 3;  i <= n;  ++i) {
                result = result.multiply(BigInteger.valueOf(i));
            }
            return result;
        }
    }


    /** Computes and returns n! */
    private static BigInteger factorialThreaded(int n, int nThreads) {
        if (n <= 1) {
            return BigInteger.ONE;
        }
        else {
            FactThread.INCREMENT = nThreads;

            // Create and start nThreads - 1 each working
            // on a different part of the factorial problem.
            FactThread[] threads = new FactThread[nThreads - 1];
            int i;
            for (i = 0;  i < threads.length;  ++i) {
                threads[i] = new FactThread(2 + i, n);
                threads[i].start();
            }

            // Use the current thread to compute one part
            // of the factorial problem.
            FactThread last = new FactThread(2 + i, n);
            last.run();

            // All the threads will finish at about the same
            // time, but we must wait until they are all done.
            boolean done;
            do {
                done = true;
                for (i = 0;  i < threads.length;  ++i) {
                    done &= threads[i].done;
                }
                if (!done) {
                    try { Thread.sleep(5); }
                    catch (InterruptedException ex) { }
                }
            } while (!done);

            // Combine the results from all threads.
            BigInteger product = last.product;
            for (FactThread thread in threads) {
                product = product.multiply(thread.product);
            }

            return product;
        }
    }


    private static final class FactThread extends Thread {
        static int INCREMENT;

        private final int start, end;  // inclusive
        BigInteger product;
        boolean done;

        public FactThread(int start, int end) {
            this.start = start;
            this.end = end;
        }

        public void run() {
            /* It is slightly faster to multiply from small numbers
             * to large numbers because smaller numbers stored in a
             * BigInteger require less memory. */

            BigInteger result = BigInteger.valueOf(start);
            for (int i = start + INCREMENT; i <= end; i += INCREMENT) {
                result = result.multiply(BigInteger.valueOf(i));
            }

            this.product = result;
            this.done = true;
        }
    }
}