Java Threads

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 improves the performance of the program.

Thread Life Cycle

A UML state diagram showing the life cycle of a Java thread

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

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 .start() 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

A UML class diagram showing an inheritance hieararchy for two
different ways to create a thread.

There are two ways to create a thread in Java.

  1. Write a class that extends java.lang.Thread (MyThread on the left hand side of the diagram) and create an object from it.
    MyThread mt = new MyThread();
    mt.start();
  2. Write a class that implements java.lang.Runnable (MyRunnable on the right hand side of the diagram), create an object from it, then create a java.lang.Thread object passing it the custom runnable object.
    MyRunnable mr = new MyRunnable();
    Thread t = new Thread(mr);
    t.start();

Regardless of which method you use to create a thread, you must write the code you want executed as part of the thread in a public void run() method. When this run method ends the thread terminates.

Example

The following Java example shows two ways to compute n! (factorial) for large n. The first way is shown in function factorial() starting at line #64 and doesn't use threads. The second way is shown in function factorialThreaded() and class FactThread starting at line #82. On my computer which has an Intel ™ CPU with 4 cores, the second method is about four times faster than the first method for large n because the calculations are spread across 4 threads each of which executes on a separate core.

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 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();
			}
			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.
			for (i = 0;  i < threads.length;  ++i) {
				last.product = last.product.multiply(threads[i].product);
			}

			return last.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;
		}
	}
}

Copyright © 2011, Maia L.L.C.  All rights reserved.

Maia L.L.C. and its employees have used their best efforts in preparing this article. These efforts include the development, research, and testing of the theories and computer programs in this article to determine their correctness. Maia L.L.C. makes no warranty of any kind, expressed or implied, with regard to these programs or the documentation contained in this article. Maia L.L.C. shall not be liable in any event for incidental or consequential damages in connection with, or arising out of, the furnishing, performance, or use of these programs.