Using Threads in your Java applications Java 05.05.2018

Conceptually, a thread is a flow of control within a program. A thread is similar to the more familiar notion of a process, except that threads within the same application are much more closely related and share much of the same state. It’s kind of like a golf course, which many golfers use at the same time. The threads cooperate to share a working area. They have access to the same objects, including static and instance variables, within their application. However, threads have their own copies of local variables, just as players share the golf course but do not share some personal items like clubs and balls.

java_thread1.png
  • (a) Here multiple threads are running on multiple CPUs.
  • (b) Here multiple threads share a single CPU.

In single-processor systems, as shown in (b), the multiple threads share CPU time, known as time sharing, and the operating system is responsible for scheduling and allocating resources to them. This arrangement is practical, because most of the time the CPU is idle. It does nothing, for example, while waiting for the user to enter data.

Multithreading can make your program more responsive and interactive, as well as enhance performance. For example, a good word processor lets you print or save a file while you are typing. In some cases, multithreaded programs run faster than single-threaded programs even on single-processor systems. Java provides exceptionally good support for creating and running threads and for locking resources to prevent conflicts.

Multiple threads in an application have the same problems as the golfers—in a word, synchronization. Just as you can’t have two sets of players blindly playing the same green at the same time, you can’t have several threads trying to access the same variables without some kind of coordination. Someone is bound to get hurt. A thread can reserve the right to use an object until it’s finished with its task, just as a golf party gets exclusive rights to the green until it’s done. And a thread that is more important can raise its priority, asserting its right to play through.

In a running Java program, all code is executed in threads and within a thread everything happens sequentially, one instruction after another. When JVM launches, it creates one thread for the main method to be executed in. From there, new threads can be created to execute code in parallel to the main one. The most basic way to do that is to use the Thread class.

Java offers a Thread class that can be used to launch new threads, wait for them to finish, or interact with them in more advanced ways.

Threads can be created two ways, either by extending java.lang.Thread or by implementing java.lang.Runnable.

Extending the Thread class and overriding the run() method can create a threadable class. This is an easy way to start a thread:

class Player extends Thread {
    public void run() {
        System.out.println("Strike");
    }
}

Player miki = new Player();
miki.run();

Another way to create a thread, you need to define a block of code by implementing the Runnable interface, which only has one abstract method run. An instance of this implementation can then be passed to the Thread‘s constructor.

Let’s start with an example that simulate three strike and waits for half a second after each strike.

class Player implements Runnable {
    private final int id;

    public Player(int id) {
        this.id = id;
   }

    @Override
    // This function will be executed in parallel
    public void run() {
        try {
            // Strike a ball three times
            for (int i = 0; i < 3; i++) {
                System.out.println("Strike " + i + " from Thread " + id);
                // Wait for half a second (500ms)
                Thread.sleep(500);
            }
        } catch (InterruptedException ex) {
            System.out.println("Thread was interrupted");
        }
    }

}

The InterruptedException is thrown by Thread.sleep().

We can use the Runnable implementation to create a Thread instance.

Thread thread = new Thread(new Player(1));

With the thread in hand, it is time to launch it:

System.out.println("main() started");
Thread thread = new Thread(new Player(1));
thread.start();
System.out.println("main() finished");

main() started
Strike 0 from Thread 1
main() finished
Strike 1 from Thread 1
Strike 2 from Thread 1

Notice that strikes from the main method and the thread we started are interleaving. This is because they run in parallel and their executions interleave unpredictably. In fact chances are, you will see slightly different output each time you run the program. At the same time, instructions within a single thread are always executed in the expected order as you can see from the increasing strike numbers.

So when does a thread finish? It happens in one of two cases:

  • all instructions in the Runnable are executed
  • an uncaught exception is thrown from the run method

One common task that we need to do with a thread is to wait until it is finished. In Java, this is pretty straightforward. All we need to do is to call the join method on a thread instance:

System.out.println("main() started");
Thread thread = new Thread(new Player(1));
thread.start();
System.out.println("main() is waiting");
thread.join();
System.out.println("main() finished");

In this case, the calling thread will be blocked until the target thread is finished. When the last instruction in the target thread is executed the calling thread is resumed:

main() started
main() is waiting
Strike 0 from Thread 1
Strike 1 from Thread 1
Strike 2 from Thread 1
main() finished

Notice that "main() finished" is printed after all messages from the Player. Using the join() method this way we can ensure that some operations are executed strictly after all instructions in a particular thread. If we call join on a thread that has already finished the call returns immediately and the calling thread is not being paused. This makes it easy to wait for several threads, just by looping over a collection of them and calling join on each.

Thread States

Tasks are executed in threads. Threads can be in one of five states: New, Ready, Running, Blocked, or Finished.

java_thread2.png

So, a thread is always in one of the following six states (Lifecycle of a Thread):

  • New. A new thread begins its life cycle in the new state. It remains in this state until the program starts the thread.
  • Runnable. After a newly born thread is started, the thread becomes runnable. A thread in this state is considered to be executing its task.
  • Blocked. A thread is in the blocked state when it tries to access a protected section of code that is currently locked by some other thread. When the protected section is unlocked, the schedule picks one of the thread which is blocked for that section and moves it to the runnable state.
  • Waiting. Sometimes, a thread transitions to the waiting state while the thread waits for another thread to perform a task. A thread transitions back to the runnable state only when another thread signals the waiting thread to continue executing.
  • Timed-waiting. A runnable thread can enter the timed waiting state for a specified interval of time. A thread in this state transitions back to the runnable state when that time interval expires or when the event it is waiting for occurs.
  • Terminated. A runnable thread enters the terminated state when it completes its task or otherwise terminates.

Enumeration Thread.state provides six thread states, as described below

  • NEW - a thread that is created but not started
  • RUNNABLE - a thread that is available to run
  • BLOCKED - an "alive" thread that is blocked waiting for a monitor lock
  • WAITING - an "alive" thread that calls its own wait() or join() while waiting on another thread
  • TIMED_WAITING - an "alive" thread that is waiting on another thread for a specified period of time; sleeping
  • TERMINATED - a thread that has completed

Thread Priorities

The valid range of priority values is typically 1 through 10, with a default value of 5. Thread priorities are one of the least portable aspects of Java, as their range and default values can vary among Java Virtual Machines (JVMs). Using MIN_PRIORITY, NORM_PRIORITY, and MAX_PRIORITY can retrieve priorities.

System.out.print(Thread.MAX_PRIORITY);

Lower priority threads yield to higher priority threads.

Common Methods

Following is common methods used for threads from the Thread class.

  • getPriority() returns the thread’s priority
  • getState() returns the thread’s state
  • interrupt() interrupts the thread
  • isAlive() returns the thread’s alive status
  • isInterrupted() checks for interruption of the thread
  • join() causes the thread that invokes this method to wait for the thread that this object represents to finish
  • setPriority(int) sets the thread’s priority
  • start() places the thread into a runnable state
  • sleep(long) puts a thread to sleep for a specified time in milliseconds
  • yield() causes a thread to pause temporarily and allow other threads to execute.

Using the join() method in a program is useful if one of the threads cannot proceed until another thread has finished executing.

Following is common methods used for threads from the Object class.

  • notify() tells a thread to wake up and run
  • notifyAll() tells all threads that are waiting on a thread or resource to wake up, and then the scheduler will select one of the threads to run
  • wait() pauses a thread in a wait state until another thread calls notify() or notifyAll()

You must consider the following two rules before you call the wait() method of an object.

Rule #1. The call to the wait() method must be placed inside a synchronized method (static or non-static) or a synchronized block.

Rule #2. The wait() method must be called on the object whose monitor the current thread has acquired. It throws a java.lang.InterruptedException. The code that calls this method must handle this exception. The wait() method throws an IllegalMonitorStateException when the current thread is not the owner of the object’s monitor.

Synchronization

It’s possible for data to effectively become corrupted when it’s modified by more than one thread simultaneously. However, Java’s synchronized keyword provides an easy way for you to prevent this from happening by allowing you to define methods and blocks of code that can be executed by only one thread at a time. In effect, the synchronized keyword locks the method or block of code while it’s being executed by one thread so that no other threads are allowed to enter until the first thread has exited the method or block.

Each instance of java.lang.Object or one of its subclasses (in other words, every Java object) maintains a lock (or monitor or mutex (mutually exclusive lock)), and the synchronized keyword is always implicitly or explicitly associated with an instance of Object (primitives can’t be used). Before a thread can enter a synchronized method or section of code, it must obtain the monitor of the object associated with that code. If one thread obtains an object’s monitor and a second thread attempts to do so, the second thread becomes blocked and its execution is suspended until the monitor becomes available. In addition to the monitor, each object maintains a list of threads that are blocked because they’re waiting on the object’s monitor. If a thread can’t obtain an object’s monitor, it’s automatically put on the list, and once the monitor becomes available, one of the threads in the list will be given the monitor and allowed to continue execution. This behavior occurs when you use the synchronized keyword, and you don’t need to explicitly obtain or release an object’s monitor. Instead, it will be automatically obtained (if possible) when a thread enters a synchronized method or block of code and released when the thread exits that code block or method.

The synchronized keyword provides a means to apply locks to blocks and methods. Locks should be applied to blocks and methods that access critically shared resources. These monitor locks begin and end with opening and closing braces. Following are some examples of synchronized blocks and methods.

Object instance t with a synchronized lock:

synchronized (t) {
    // block body
}

Object instance this with a synchronized lock:

synchronized (this) {
    // block body
}

Method raise() with a synchronized lock:

// Equivalent code segment 1
synchronized void raise() {
    // method body
}

// Equivalent code segment 2
void raise() {
    synchronized (this) {
        // method body
    }
}

Static method calibrate() with a synchronized lock:

class Telescope {
    synchronized static void calibrate() {
        // method body
    }
}

Volatile variables

I discussed the use of the synchronized keyword in previous sections. Two things happen when a thread executes a synchronized method/block.

  • The thread must obtain the monitor lock of the object on which the method/block is synchronized.
  • The thread’s working copy of the shared variables is updated with the values of those variables in the main memory just after the thread gets the lock. The values of the shared variables in the main memory are updated with the thread’s working copy value just before the thread releases the lock. That is, at the start and at the end of a synchronized method/block, the values of the shared variables in thread’s working memory and the main memory are synchronized.

What can you do to achieve only the second point without using a synchronized method/block? That is, how can you keep the values of variables in a thread’s working memory in sync with their values in the main memory? The answer is the keyword volatile. You can declare a variable volatile like so:

volatile boolean flag = true;

For every read request for a volatile variable, a thread reads the value from the main memory. For every write request for a volatile variable, a thread writes the value to the main memory. In other words, a thread does not cache the value of a volatile variable in its working memory. Note that using a volatile variable is useful only in a multi-threaded environment for variables that are shared among threads. It is faster and cheaper than using a synchronized block.

You can declare only a class member variable (instance or static fields) as volatile. You cannot declare a local variable as volatile because a local variable is always private to the thread, which is never shared with other threads. You cannot declare a volatile variable final because the volatile keyword is used with a variable that changes.

You can use a volatile variable to stop a thread by using the variable’s value as a flag. If the flag is set, the thread can keep running. If another thread clears the flag, the thread should stop. Since two threads share the flag, you need to declare it volatile, so that on every read the thread will get its updated value from the main memory.

Following listing demonstrates the use of a volatile variable. If the keepRunning variable is not declared volatile, the JVM is free to run the while loop in the run() method forever, as the initial value of keepRunning is set to true and a thread can cache this value in its working memory. Since the keepRunning variable is declared volatile, the JVM will read its value from the main memory every time it is used. When another thread updates the keepRunning variable’s value to false using the stopThread() method, the next iteration of the while loop will read its updated value and stop the loop.

public class VolatileVariable extends Thread {
    private volatile boolean keepRunning = true;
    @Override
    public void run() {
        System.out.println("Thread started...");
        // keepRunning is volatile. So, for every read, the thread reads its
        // latest value from the main memory
        while (keepRunning) {
            try {
                System.out.println("Going to sleep ...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Thread stopped...");
    }
    public void stopThread() {
        this.keepRunning = false;
    }
    public static void main(String[] args) {
        // Create the thread
        VolatileVariable vv = new VolatileVariable();
        // Start the thread
        vv.start();
        // Let the main thread sleep for 3 seconds
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Stop the thread
        System.out.println("Going to set the stop flag to true...");
        vv.stopThread();
    }
}
Thread started...
Going to sleep ...
Going to sleep ...
Going to sleep ...
Going to set the stop flag to true...
Thread stopped...

Concurrent Utilities

Java 2 SE 5.0 introduced utility classes for concurrent programming. These utilities reside in the java.util.concurrent package, and they include executors, concurrent collections, synchronizers, and timing utilities.

The java.util.concurrent package and its two subpackages, java.util.concurrent.atomic and java.util.concurrent.locks, include very useful concurrency constructs. You use them only when you are developing an advanced level multi-threaded program. We can categorize these concurrency features into four categories:

  • Atomic variables
  • Locks
  • Synchronizers
  • Concurrent collections

Executors

Above you have learned how to define a task class by implementing java.lang.Runnable, and how to create a thread to run a task like this:

Runnable task = new TaskClass(task);
new Thread(task).start();

This approach is convenient for a single task execution, but it is not efficient for a large number of tasks, because you have to create a thread for each task. Starting a new thread for each task could limit throughput and cause poor performance. Using a thread pool is an ideal way to manage the number of tasks executing concurrently. Java provides the Executor interface for executing tasks in a thread pool and the ExecutorService interface for managing and controlling tasks. ExecutorService is a subinterface of Executor.

The class Executors provides factory (object creator) methods and utility methods. Of them, the following are supplied to create thread pools:

  • newCachedThreadPool() creates an unbounded thread pool that automatically reuses threads
  • newFixedThreadPool(int nThreads) creates a fixed-size thread pool that automatically reuses threads off a shared unbounded queue
  • newScheduledThreadPool(int corePoolSize) creates a thread pool that can have commands scheduled to run periodically or on a specified delay
  • newSingleThreadExecutor() creates a single-threaded executor that operates off an unbounded queue
  • newSingleThreadScheduledExecutor() creates a single-threaded executor that can have commands scheduled to run periodically or by a specified delay

The following example demonstrates usage of the newFixedThreadPool factory method:

public class ThreadPoolExample {
    public static void main() {
        // Create tasks (class RTask implements Runnable')
        RTask t1 = new RTask("thread1");
        RTask t2 = new RTask("thread2");

        // Create thread manager
        ExecutorService threadExecutor = Executors.newFixedThreadPool(2);

        // Make threads runnable
        threadExecutor.execute(t1);
        threadExecutor.execute(t2);

        // Shut down threads
        threadExecutor.shutdown();
    }
}

The Fork/Join Framework

The fork/join framework is an implementation of the executor service whose focus is to solve those problems efficiently, which may use the divide-and-conquer algorithm by taking advantage of the multiple processors or multiple cores on a machine. The framework helps solve the problems that involve parallelism. Typically, the fork/join framework is suitable in a situation where

  • A task can be divided in multiple subtasks that can be executed in parallel.
  • When subtasks are finished, the partial results can be combined to get the final result.

The fork/join framework creates a pool of threads to execute the subtasks. When a thread is waiting on a subtask to finish, the framework uses that thread to execute other pending subtasks of other threads. The technique of an idle thread executing other thread’s task is called work-stealing. The framework uses the work-stealing algorithm to enhance the performance. The following four classes in the java.util.concurrent package are central to learning the fork/join framework:

  • ForkJoinPool
  • ForkJoinTask
  • RecursiveAction
  • RecursiveTask

Concurrent Collections

Even though collection types can be synchronized, it is best to use concurrent thread-safe classes that perform equivalent functionality, as represented below.

Collection class Thread-safe equivalent
HashMap ConcurrentHashMap
TreeMap ConcurrentSkipListMap
TreeSet ConcurrentSkipListSet
Map subtypes ConcurrentMap
List subtypes CopyOnWriteArrayList
Set subtypes CopyOnWriteArraySet
PriorityQueue PriorityBlockingQueue
Deque BlockingDeque
Queue BlockingQueue

Synchronizers

Synchronizers are special-purpose synchronization tools. Available synchronizers are listed in following table.

Synchronizer Description
Semaphore Maintains a set of permits
CountDownLatch Implements waits against sets of operations being performed
CyclicBarrer Implements waits against common barrier points
Exchanger Implements a synchronization point where threads can exchange elements

Daemon Threads

Each thread is classified as either a daemon thread or a user thread, and Thread's setDaemon() method allows you to specify the thread’s type. To use setDaemon(), you must call it before a thread is started, and passing a boolean value of true indicates that the thread should be a daemon thread, while false (the default) indicates it should be a user thread.

The only difference between a daemon thread and a user thread is that one type (user) prevents the Java Virtual Machine from exiting, while the other (daemon) doesn’t.

Tasks with results: Callable and Future

Because the Runnable interface was created for Thread to consume, its API doesn’t allow for direct feedback to the caller. The new Callable interface, which is effectively a replacement for Runnable, rectifies this situation by providing a call() method that both returns a result and can throw exceptions. Callable is a generic class that is parameterized by the type it returns. The following examples create a Callable that returns an integer:

class MyCallable implements Callable<Integer> {
    public Integer call() { return 2+2; }
}

// or anonymously
Callable<Integer> callable = new Callable<Integer>() {
    public Integer call() { return 2+2; }
};

There is also a convenience method for bridging Runnable to Callable in the Executor class. It takes a Runnable and a fixed value to return as a value when it completes:

Callable<Integer> callable = Executors.callable( runnable,
42 /*return value*/ );

The new Future class is used with Callable and serves as a handle to wait for and retrieve the result of the task or cancel the task before it is executed. A Future is returned by the submit() methods of an ExecutorService, which is essentially a beefed-up Executor.

Future<Integer> result = executorService.submit(callable);
int val = result.get(); // blocks until ready

Future is also a generic interface, which is parameterized by its return type. This explains the somewhat cute name. For example, a Future<Integer> could be read as "a future integer". Future has both blocking and timed-wait get() methods to retrieve the result when it is ready, as well as an isDone() test method and a cancel() method to stop the task if it hasn’t started yet. If the task has been cancelled, you get a CancellationException if you attempt to retrieve the result.

Disadvantages of Using Threads

  • Slow Initial Startup. Creating and starting a new thread is a relatively slow operation on some platforms, and in an application where performance is critical, this can be a significant drawback. Thread pooling provides a reasonably simple solution to this problem.
  • Resource Utilization. Each thread is allocated its own stack, which is an area of storage used to contain local variable values (that is, variables defined within a method) and other information related to execution. Other system resources are used in addition to the stack, although the specific amount and type of those resources used vary from one Java Virtual Machine (JVM) to the next. Although it’s typically possible to create a large number of threads, the platform you’re using may limit the number that can be created. Even if the platform doesn’t explicitly limit the number of threads you can create, there’s usually a practical limit determined by the speed of your processor(s) and the amount of available memory on your system. Although you can’t eliminate this problem, you can control it through thread pooling.
  • Increased Complexity. Thread safety usually involves designing the object so that its data can’t be read or written by one thread while another thread is in the process of modifying that data. An even more complex problem is the matter of sharing resources among multiple threads.
  • Sharing Resources.

Outcome

Nowadays, Thread is considered to be a low-level tool in multithreaded programming. Instead of explicitly creating threads you might want to use thread pools that limit the number of threads in your application and executors that allow executing asynchronous tasks.