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.

- 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.
| |||||||
△ | |||||||
┌-----┴---------┐ | |||||||
╎ | ╎ | ||||||
|
╎ ╎ ╎ ╎ ╎ |
||||||
△ | ╎ | ||||||
| | ╎ | ||||||
|
|
- Write a class that extends
java.lang.Thread
and create an object from it. This is demonstrated by theMyThread
class on the left hand side of the class diagram and the following code example.MyThread mt = new MyThread(); mt.start();
- Write a class that implements
java.lang.Runnable
, create an object from it, then create ajava.lang.Thread
object, and pass the new custom runnable object to the new Thread object. This is demonstrated by theMyRunnable
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.
| ||||||||
⯁ | ||||||||
| | ||||||||
|
◁──── |
|
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; } } }