Concurrent Programming – [ Thread ]

A thread refers to a single sequential control flow in a process, it cannot exist independently, it must be part of a process.

A process consists of memory space allocated by the operating system and contains one or more threads, it can have multiple threads concurrently, and each thread executes different tasks in parallel.

Multi-threading is a special form of multitasking, but it uses less resource overhead, it can satisfy programmers to write efficient programs to make full use of the CPU.

What You Need

  • About 13 minutes
  • A favorite text editor or IDE
  • Java 8 or later

1. A Thread’s Life Cycle

Thread is a dynamic execution process, it also has different status from birth to death.

1.1 New State

When a thread object is created using the new keyword with the Thread class or its subclasses, it enters the new state.

It remains in this state until the program starts() the thread.

1.2 Runnable State

When the thread object calls the start() method, the thread enters the runnable state.

The thread in the runnable state is in the ready queue, waiting for the scheduling of the thread scheduler in the JVM.

1.3 Running State

If the thread in the runnable state acquires CPU resources, it can execute run(), and the thread is in the running state.

1.4 Blocking State

If a thread executes methods such as sleep, and loses the occupied resources, the thread enters the blocking state from the running state.

The runnable state can be re-entered after sleep time has elapsed or device resources have been acquired.

There are three different situations :

  • Waiting : the thread in the running state executes the wait() method to make the thread enter the waiting state;
  • Blocked : the thread failed to acquire the synchronization lock (because the synchronization lock is occupied by other threads);
  • Time Waiting : A thread enters a time waiting state when an I/O request is made by calling the thread’s sleep() or join().
    When the sleep() state times out or when the join() waits for the thread to terminate or to time out, or the I/O processing is complete, then the thread re-enters the runnable state.

1.5 Terminated State

When a thread in the running state completes a task or other termination conditions occur, the thread switches to the terminated state.

1.6 Example Code Showing Thread States

public class ThreadLifeCycleDemo extends Thread {
    public void run() {
        System.out.println("Thread is running...");
        try {
            Thread.sleep(1000); // TIMED_WAITING
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread finished.");
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadLifeCycleDemo t = new ThreadLifeCycleDemo();

        System.out.println("State after creating: " + t.getState()); // NEW
        t.start();
        System.out.println("State after start(): " + t.getState()); // RUNNABLE

        Thread.sleep(100); // Small delay to let thread enter sleep
        System.out.println("State during sleep(): " + t.getState()); // TIMED_WAITING

        t.join();
        System.out.println("State after thread finished: " + t.getState()); // TERMINATED
    }
}

Below is the output of above code snippet :

State after creating: NEW
State after start(): RUNNABLE
Thread is running...
State during sleep(): TIMED_WAITING
Thread finished.
State after thread finished: TERMINATED

2. Thread Priority

Each Java thread has a priority, which helps the operating system determine the scheduling order of threads.

The priority of a Java thread is an integer in the range 1 (Thread.MIN_PRIORITY) to 10 (Thread.MAX_PRIORITY).

By default, each thread is assigned a priority 5 (NORM_PRIORITY).

Threads with higher priority are more important to the program and should be allocated processor resources before lower priority threads.

However, thread priority does not guarantee the order of thread execution and is very platform dependent.

3. Thread Creation

Java provides three ways to create a thread :

  • Implement the runnable interface;
  • Implement the callable interface;
  • Extend from the thread class.

3.1 Create A Thread By Implementing Runnable Interface

To create a thread, the easiest way is to create a class that implements the Runnable interface.

A class implementing Runnable interface needs to implement run method.

public class ThreadCreation1 {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };

        Thread t = new Thread(r, "Thread1");
        t.start();
        t.join();
    }
}

Below is the output of above code snippet :

Thread1

3.2 Create A Thread By Implementing Callable Interface

Another way to create a thread consist of implementing the Callable interface.

Compared with Runnable, Callable can have return value, and the return value is encapsulated by FutureTask.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadCreation2 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<String> c = new Callable<>() {
            @Override
            public String call() throws Exception {
                return Thread.currentThread().getName();
            }
        };

        FutureTask<String> task = new FutureTask<>(c);

        Thread t = new Thread(task, "Thread1");

        t.start();

        System.out.println(task.get());
    }
}

The output of above code snippet is below :

Thread1

3.3 Create A Thread By Extending Thread Class

The third way to create a thread is to create a new class that extends the Thread class, and then create an instance of that class.

The class extending Thread class must override run method.

public class ThreadCreation3 {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread("Thread1");
        t.start();
        t.join();
    }

    private static class MyThread extends Thread {
        MyThread(String threadName) {
            super(threadName);
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

Below is the output of above code snippet :

Thread1

3.4 Comparison Of 3 Ways To Create A Thread

  • When creating a thread by implementing Runnable and Callable interfaces, the thread class only implements Runnable or Callable interface, it can also extend other classes.
  • When creating a thread by extending Thread class, if it needs to access the current thread, we can just use ‘this’ instead of using Thread.currentThread() method.

4. Catch Exceptions From A Thread

By default run method of a thread doesn’t throw any exception.

So all checked exceptions inside the run method has to be caught and handled.

For runtime exceptions or if we throw any exception, it needs to use UncaughtExceptionHandler.

public class ThreadExceptionHandling2 {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () -> {
            throw new RuntimeException("exception in runnable");
        };

        Thread t = new Thread(r);

        t.setUncaughtExceptionHandler((Thread tt, Throwable e) -> {
            System.out.println("Exception caught in thread " + tt.getName() + " : " + e.getMessage());
        });

        t.start();

        t.join();
    }
}

Below is the output of above code snippet :

Exception caught in thread Thread-0 : exception in runnable

In case of creating a thread by implementing callable interface, it is not necessary to use UncaughtExceptionHandler.

Because we can use try catch around the future task to which the callable is attached.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadExceptionHandling1 {
    public static void main(String[] args) {
        Callable<Void> c = () -> {
            throw new RuntimeException("exception in callable");
        };

        FutureTask<Void> f = new FutureTask<>(c);

        Thread t = new Thread(f);

        t.start();

        try {
            f.get();
        } catch (ExecutionException | InterruptedException e) {
            System.out.println("exception caught in thread " + t.getName() + " : " + e.getCause().getMessage());
        }
    }
}

Below is the output of above code snippet :

exception caught in thread Thread-0 : exception in callable

5. Daemon Thread

A daemon thread is a thread that provides services in the background while the program is running.

When all non-daemon threads end, the program terminates and all daemon threads are killed no matter if they are still alive or not.

We can use the setDaemon method to set a thread as a daemon thread.

import java.util.concurrent.TimeUnit;

public class DaemonThreadExample {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () -> {
            int count = 0;
            while (true) {
                System.out.println(Thread.currentThread().getName() + " : " + count++);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread t = new Thread(r);
        t.setDaemon(true);
        t.start();

        TimeUnit.SECONDS.sleep(3);

        System.out.println("main thread finishes, so daemon thread is also forced to terminate");
    }
}

Below is the output of above code snippet :

Thread-0 : 0
Thread-0 : 1
Thread-0 : 2
main thread finishes, so daemon thread is also forced to terminate

6. Methods Of Thread Class

Thread class defines many methods for managing threads.

Below are some of them that we use frequently.

6.1 sleep

The sleep method will sleep the currently executing thread for x milliseconds.

This method may throw InterruptedException.

public class MethodOfThreadClass1 {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () -> {
            try {
                long start = System.currentTimeMillis();
                Thread.sleep(3000);
                long end = System.currentTimeMillis();
                System.out.println(
                        Thread.currentThread().getName() + " has sleeped for "
                                + (end - start) + " milliseconds");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread t = new Thread(r);
        t.start();
        t.join();
    }
}

Below is the output of above code snippet :

Thread-0 has sleeped for 3000 milliseconds

6.2 yield

The yield method hints to the thread scheduler that the current thread is willing to pause its execution to give other threads of the same priority a chance to run.

This method is only a suggestion to the thread scheduler, and only other threads with the same or higher priority can run.

If no other thread of the same or higher priority is ready to run, the current thread may continue running.

The thread scheduler is free to adhere or ignore this information and in fact, has varying behavior depending upon the operating system.

For instance, in below example, it is not certain that the thread 1 give up its execution even if yield method is called.

public class MethodOfThreadClass2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("t1");
        MyThread t2 = new MyThread("t2");

        t1.start();
        t1.yield();
        t2.start();

        t1.join();
        t2.join();
    }

    private static class MyThread extends Thread {
        MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + " in control");
            }
        }
    }
}

Below is the output of above code snippet :

t2 in control
t2 in control
t1 in control
t1 in control
t2 in control
t1 in control

6.3 interrupt

The interrupt method is used to interrupt a thread.

If a thread is in sleeping or waiting state, then using the interrupt method can interrupt the thread’s execution by throwing InterruptedException.

public class MethodOfThreadClass3 {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () -> {
            try {
                Thread.sleep(3000);
                System.out.println("This line will not be printed beacause the interruption of thread");
            } catch (InterruptedException e) {
                throw new RuntimeException(Thread.currentThread().getName() + " has been interrupted !", e);
            }
        };

        Thread t = new Thread(r);

        t.start();

        t.interrupt();

        t.join();
    }
}

Below is the output of above code snippet :

Exception in thread "Thread-0" java.lang.RuntimeException: Thread-0 has been interrupted !
        at MethodOfThreadClass3.lambda$0(MethodOfThreadClass3.java:8)
        at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.InterruptedException: sleep interrupted
        at java.base/java.lang.Thread.sleep0(Native Method)
        at java.base/java.lang.Thread.sleep(Thread.java:509)
        at MethodOfThreadClass3.lambda$0(MethodOfThreadClass3.java:5)
        ... 1 more

If the thread is not in the sleeping or waiting state, then calling the interrupt method doesn’t interrupt the thread but sets the interrupt flag to true.

public class MethodOfThreadClass4 {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () -> {
            while (!Thread.interrupted()) {
                System.out.println(Thread.currentThread().getName() + " is running");
            }
            System.out.println(Thread.currentThread().getName() + " stops running");
        };

        Thread t = new Thread(r);

        t.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t.interrupt();

        t.join();
    }
}

Below is the output of above code snippet :

Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 stops running

6.4 join

In a thread, calling join method of another thread will suspend the current thread until the target thread ends.

In below example, although thread t2 starts first, because thread t1’s join method is called in t2, so t2 will wait for t1 to end before continuing to execute, so the output of t1 can be guaranteed before t2.

public class MethodOfThreadClass5 {
    public static void main(String[] args) {
        Runnable r1 = () -> {
            System.out.println(Thread.currentThread().getName());
        };

        Thread t1 = new Thread(r1, "t1");

        Runnable r2 = () -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        };

        Thread t2 = new Thread(r2, "t2");

        t2.start();
        t1.start();
    }
}

The output of above code snippet is below :

t1
t2

6.5 wait, notify, notifyAll

Those methods are all part of Object class, not Thread class, but they are used tightly in the context of multi-threads.

Calling wait method makes the thread wait for a certain condition to be met, and the thread will be suspended while waiting.

When the operation of other threads makes this condition met, other threads will call notify or notifyAll method to wake up the suspended thread.

It can only be used in synchronized methods or synchronized control blocks, otherwise an IllegalMonitorStateException will be thrown at runtime.

The suspended thread releases the lock while calling wait method.

This is because if the lock is not released, other threads cannot enter the synchronization method or synchronization control block of the object, and then notify or notifyAll method cannot be executed to wake up the suspended thread, resulting in a deadlock.

Below is an simple example of producer-consumer pattern.

Producer and Consumer have a shared container object which contains an int array.

Producer writes 1 into the container’s array every time and Consumer reset the value from 1 to 0.

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class MethodOfThreadClass6 {
    public static void main(String[] args) {
        Container container = new Container(5);

        Runnable p = () -> {
            for (int i = 0; i < 6; i++) {
                container.add();
            }
        };

        Runnable c = () -> {
            for (int i = 0; i < 5; i++) {
                container.remove();
            }
        };

        Thread producer = new Thread(p);
        Thread consumer = new Thread(c);

        producer.setDaemon(true);
        consumer.setDaemon(true);

        producer.start();
        consumer.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        container.display();
    }

    private static class Container {
        private int[] content;
        private int index;

        Container(int capacity) {
            this.content = new int[capacity];
            this.index = 0;
        }

        synchronized void add() {
            while (this.content[this.content.length - 1] == 1) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            this.content[this.index++] = 1;

            notify();
        }

        synchronized void remove() {
            while (this.content[0] == 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            this.content[--this.index] = 0;

            notify();
        }

        void display() {
            IntStream.range(0, this.content.length).forEach(index -> {
                System.out.print(this.content[index]);

                if (index == this.content.length - 1) {
                    System.out.println();
                } else {
                    System.out.print(",");
                }
            });
        }
    }
}

Below is the output of above code snippet :

1,0,0,0,0