Thread Management

2016/4/6 posted in  Java7 Concurrency Cookbook

Introduction

Inside a process, we can have various simultaneous tasks. The concurrent tasks that run inside a process are called threads.

We have two ways of creating a thread in Java:

  • Extending the Thread class and overriding the run() method
  • Building a class that implements the Runnable interface and then creating an object of the Thread class passing the Runnable object as a parameter

Calculator

public class Calculator implements Runnable
{
    private int number;

    public Calculator (int number)
    {
        this.number = number;
    }

    @Override
    public void run ()
    {
        for (int i = 1; i <= 10; i ++)
        {
            System.out.printf("%s: %d * %d = %d \n", Thread.currentThread().getName(), number, i, i * number);
        }
    }
}

Main

public class Main
{
    public static void main (String[] args)
    {
        for (int i = 1; i <= 10; i ++)
        {
            Calculator calculator = new Calculator(i);
            Thread thread = new Thread(calculator);
            thread.start();
        }
    }
}

Every Java program has at least on execution thread. When you run the program, the JVM runs this execution thread that calls the main() method of the program.

When you call the start() method of a Thread object, we are creating another execution thread.

A Java program ends when all its threads finish. If the initial thread ends, the rest of the threads will continue with their execution until they finish. If one of the threads use the System.exit() instruction to end the execution of the program, all the threads end their execution.

Creating an object of the Thread class doesn't create a new execution thread. Also, calling the run() method of a class that implements the Runnable interface doesn't create a new execution thread. Only calling the start() method creates a new execution thread.

Getting and setting thread information

The Thread class saves some information attributes that can help us to identify a thread. These attributes are:

  • ID: stores a unique identifier for each Thread
  • Name: stores the name of the Thread
  • Priority: stores the priority of the Thread object. Threads can have a priority between 1 and 10, where 1 is the lowest.
  • Status: stores the status of Thread. Thread can be in one of these six states: new, runnable, blocked, waiting, time waiting, or terminated.

The threads with the highest priority end before the ones with the lowest priority.

Interrupting a thread

A Java program with more than one execution thread only finishes when the execution of all of its threads end.

Java provides the interruption mechanism to indicate to a thread that we want to finish it. Thread has to check if it has been interrupted or not, and it can decide if it responds to the finalization request or not. Thread can ignore it adn continue with its execution.

The Thread class has an attribute that stores a boolean value indicating whether that thread has been interrupted or not. When you call the interrupt() method of a thread, you set that attribute to true. The isInterrupted() method only returns the value of that attribute.

You can throw InterruptedException when you detect the interruption of the thread and catch it in the run() method.

Sleeping and resuming a thread

Sometimes, the thread does nothing. During this time, the thread doesn't use any resources of the computer. After this time, the thread will be ready to continue with its execution when the JVM chooses it to be executed. You can use the sleep() method of the Thread class for this purpose.

Another possibility is to use the sleep() method of an element of the TimeUnit enumeration. This method uses the sleep() method of the Thread classes to put the current thread to sleep, but it receives the parameter in the unit that it represents and converts it to milliseconds.

When you call the sleep() method, Thread leaves the CPU and stops its execution for a period of time. During this time, it's not consuming CPU time, so the CPU can be executing other tasks.

When Thread is sleeping and is interrupted, the method throws an InterruptedException immediately and doesn't wait until the sleeping time finishes.

Waiting for the finalization of a thread

We may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initializaion tasks as threads and wait for its finialization before continuing with the rest of the program.

For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.

Instead of waiting indefinitely for the finalization of the thread called, the calling thread waits for the milliseconds specified as a parameter of the method.

join(long milliseconds)

join(long milliseconds, long nanos)

For example, if the object thread1 has the code, thread2.join(1000), the thread thread1 suspends its execution until one of these two conditions is true:

  • thread2 finishes its execution
  • 1000 milliseconds have been passed

Creating and running a daemon thread

Deamon thread: has very low priority and normally only executes when no other thread of the same program is running. When daemon threads are the only threads running in a program, the JVM ends the program finishing thses thread.

They can't do important jobs because we don't know when they are going to have CPU time and they can finish any time if there aren't any other threads running. A typical example of these kind of threads is the Java garbage collector.

You only can call the setDaemon() method before you call the start() method. Once the thread is running, you can't modify its daemon status.

Processing uncontrolled exceptions in a thread

When a checked exception is thrown inside the run() method of a Thread object, we have to catch and treat them, because the run() method doesn't accept a throws clause.

When an unchecked exception is thrown inside the run() method of a Thread object, the default behavior is to write the stack trace in the console and exit the program.

When an exception is thrown in a thread and is not caught, the JVM checks if the thread has an uncaught execption handler set by the corresponding method. If it has, the JVM invokes this method with the Thread object and Exception as arguments.

If the thread has not got an uncaught exception handler, the JVM prints the statck trace in the console and exits the program.

Using local thread variables

If you create an object of a class that implements the Runnable interface and then start various Thread objects using the same Runnable object, all the threads share the same attributes. This meanns that, if you change an attribute in a thread, all the threads will be affected by this change.

Thread-local variables: sometimes, you will be interested in having an attribute that won't be shared between all the threads that run the same object.

private static ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
   protected Date initialValue() {
       return new Date();
   }
};

Thread-local variables store a value of an attribute for each Thread that uses one of these variables. You can read the value using the get() method and changes the value using the set() method. The first time you access the value of a thread-local variable, if it has no value for the Thread object that it is calling, the thread-local variable calls the initialValue() method to assign a value for that Thread and returns the inital value.

The thread-local class provides the remove() method that deletes the value stored in the thread-local variable for the thread that it's calling.

If a thread A has a value in a thread-local variable and it creates another thread B, the thread B will have the same value as the thread A in the thread-local varaible. You can override the childValue() method that is called to initalize the value of the child thread in the thread-local variable.

Grouping threads into a group

threat the threads of a group as a single unit and provides access to the Thread objects that belong to a group to do an operation with them.

For example, you have some threads doing the same task and you want to control them, irrespective of how many threads are still running, the status of each one will interrupt all of them with a single call.

Java provides the ThreadGroup class to work with groups of threads. A ThreadGroup object can be formed by Thread objects and by another ThreadGroup object, generating a tree structure of threads.

Processing uncontrolled exceptions in a group of threads

Java implements an exception-based mechanism to manage error situations. Those exceptions are thrown by the Java classes when an error situation is detected.

Java also provides a mechanism to capture and process those exceptions. There are exceptions that must be captured or re-thrown using the throws clause of a method. These exceptions are called checked exceptions. Those not are unchecked exceptions.

When an uncaught exception is thrown in Thread, the JVM looks for three possible handlers for this exception.

  • it looks for the uncaught exception handler of the thread
  • If this handler doesn't exist, then the JVM looks for the uncaught exception handler for the ThreadGroup class of the thread
  • If this method doesn't exist, the JVM looks for the default uncaught exception handler

Creating threads through a factory

With this factory, we centralize the creation of objects with some advantages:

  • It's easy to change the class of the objects created or the way we create these objects
  • It's easy to limit the creation of objects for limited resources. For example, we can only have n objects of a type
  • It's easy to generate statistical data about the creation of the objets

Java provides an interface, the ThreadFactory interfaces to implement a Thread object factory.

The ThreadFactory interface has only one methods called newThread. Most basic ThreadFactory has only one line:

return new Thread(r);

You can improve this implementation by adding some variants by:

  • Creating personalized threads, using a special format for the name or even creating our own thread class that inherits the Java Thread class
  • Saving thread creation statistics
  • Limiting the number of threads created
  • Validating the creation of the threads
  • ...