Java Concurrency - Basics of Threads
Java Thread
objects allow us to run our code in separate threads. When an application starts JVM creates the initial thread named main
. The main method is run on the main thread. Inside the application we can create new threads to execute other tasks in parallel with the main thread.
Java uses native operating system threads. So one java thread is mapped by one OS thread.
Creating Threads
The constructor of the Thread
class takes a Runnable
object. Runnable interface has an abstract run
method which is called by Thread#start()
method. It object can be instantiated by a lambda, anonymous class or a class which implements Runnable method.
Using lambdas are generally easier and more compact:
Thread thread = new Thread(() -> {
// content of run command
});
thread.start();
Thread lives as long as the its run hook method has not returned. The scheduler can suspend and run the Thread many times. For a thread to execute forever, it needs an infinite loop that prevents it from returning.
Join
method allows one thread to wait for the completion of another. This is a simple form of barrier synchronisation.
Java Thread Types: User and Daemon Threads
When JVM start it contains a single User thread, named Main thread. The main difference between User and Daemon threads are what happens when they exit.
- A user thread continues its lifecycle even if the main thread exits.
- However all Daemon threads terminates when all the user threads exits.
- JVM itself exits when all the user threads has exited.
Thread class contains boolean daemon
field to specify whether the thread is daemon. It can be set at the time of creation by the constructor or by setter method.
Thread thread = new Thread(getRunnable());
thread.setDaemon(true);
thread.start();
By default daemon field is false, so most of the Threads that we generate is a User Thread. Threads copy the isDaemon
status of the parent threat if it is not specified. Java uses Daemon thread in some places such as ForkJoinPool
and Timer
. To illustrate we can use the following example:
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// runDeamonThread();
runUserThread();
System.out.println(getCurrentThreadName() + " exits");
}
private static void runDeamonThread() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newWorkStealingPool(10);
executorService.execute(getRunnable());
}
private static void runUserThread() {
Thread thread = new Thread(getRunnable());
thread.start();
}
private static Runnable getRunnable() {
return () -> {
for (int i = 0; i <= 200; i++) {
System.out.print(".");
Thread.yield();
}
System.out.println(getCurrentThreadName() + " exits. isDeamon: " + isDaemon());
};
}
private static boolean isDaemon() {
return Thread.currentThread().isDaemon();
}
private static String getCurrentThreadName() {
return Thread.currentThread().getName();
}
}
- When we invoke
runUserThread
method it show the following example output:
................................................
main exits
........................................................................................
Thread-0 exits. isDeamon: false
- The second case is invoking the
runDeamonThread
which usesForkJoinPool
as an example of Daemon Threads. I could simply usesetDaemon(true)
method, but wanted to give an example usage. Output:
main exits
So when the main method exits, all the user threads are terminated and JVM exits and kills all daemon threads, so that we did not even have a chance to see output from daemon threads.
Stopping Threads
Compared to creating, stopping a thread is quite hard thing. Once thread starts running it diverges from the caller and it has it is own lifecycle anymore. It can either complete the task and exits or if it does a long running operation it can work forever. Java does not provides us a method (non-deprecated) to stop the thread voluntarily.
- A naive approach could be using a stop flag:
volatile boolean isStopped = false;
public void test() {
new Thread(() -> {
while (!isStopped) {
System.out.print(".");
}
System.out.println("Child Exits");
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
isStopped = true;
System.out.println("Main exits");
}
Note that the flag is volatile
in order to make its up-to-date value visible for both threads. However this approach fails if the thread is doing blocking operations such as sleep
, wait
, join
or blocking I/O operations.
2. Another way to stop the tread is to use interrupt()
method of the thread.
An interrupt request to a thread is an indication that it should stop what it is doing and do something else. It is up to the programmer to decide exactly how a thread responds to an interrupt but it is very common for the tread to terminate.
For the interrupt mechanism to work correctly, the interrupted thread must support its own interruption mechanism. There are 2 cases we can examine for interruption:
- Non Blocking and Long Running Tasks
In this case calling the thread.interrupt()
method will set the interrupt flag of the that thread but if the task itself does not check the status of the interrupted flag it will not have any impact. For example:
public void test() throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Child Starts");
while (true) {
System.out.print(".");
}
});
thread.start();
thread.interrupt();
thread.join();
System.out.println("Main exits");
}
In order for the thread to catch the interrupt, it should iteratively check the status of the interrupt flag so that it can understand if there are any pending interruption request and handle the request accordingly.
So we can check the flag in our while loop in if it is true we can return or break the loop. In the lambda expression it is not possible to throw an exception but in appropriate places we can throw InterruptedException
as well.
public void test() throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Child Starts");
while (true) {
if (Thread.interrupted()) {
break;
}
System.out.print(".");
}
System.out.println("Child exits");
});
thread.start();
thread.interrupt();
thread.join();
System.out.println("Main exits");
}
Note the Thread.interrupted()
method returns the value of the flag and clears it if it has been true. So if we want to keep the state of the Thread as interrupted for the upper level of stack, we can set it back with Thread.currentThread().interrupt();
- Blocking Tasks
If a thread frequently calls the blocking methods such as wait
, join
, sleep
, blocking I/O
which are all run interruptively, these methods internally check if they have been interrupted and if so they automatically throw InterruptedException
. This exception should be caught and handled in the appropriate context. The following example uses the interruption to break the loop in a blocking sleep
operation:
public void test() throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Child Starts");
try {
while (true) {
Thread.sleep(10000);
}
} catch (InterruptedException e) {
System.out.println("Thread interrupted: " + e.getMessage());
}
System.out.println("Child Exits");
});
thread.start();
thread.interrupt();
thread.join();
System.out.println("Main exits");
}
There are patterns for dealing with Java InterruptedException
:
- One approach is propagating the exception to the callers, so higher layer would be responsible.
- Before re-throwing, we can do task specific clean up.
- If it is not possible to re-throw, we can set the interrupted status to true again with
Thread.currentThread().interrupt()
to preserve the evidence if the higher layers want to check it.
So as a conclusion if we want to implement cancellable tasks we need to periodically check the status of the interrupt status and handle the interruption in a way that thread will exit.
Thread Groups
In order to simplify thread management, multiple threads can be organised with java.lang.ThreadGroup
objects that group related threads. Each Thread Group needs to have a parent group. In the hierarchy, there is the Main
group which is the parent of the other groups or threads we create in the program. We can create ThreadGroup
by calling its constructor with a parent group and/or name. To add the Threads in a group we need to specify the group in the Thread's constructor.
public void test() {
ThreadGroup tg1 = new ThreadGroup("Thread-group-1");
ThreadGroup tg2 = new ThreadGroup(tg1, "Thread-group-2");
Thread thread1 = new Thread(tg1,"thread-1");
Thread thread2 = new Thread(tg2,"thread-2");
Thread thread3 = new Thread(tg2,"thread-3");
thread1.start();
thread2.start();
thread3.start();
Thread[] threads = new Thread[tg2.activeCount()];
tg2.enumerate(threads);
Arrays.asList(threads).forEach(t -> System.out.println(t.getName()));
tg1.list();
}
We can iterate over the threads by calling the enumerate
method, which fills the given array with the thread references of the group.
We can implement a Thread Pool by making use of Thread Groups:
public class ThreadPool {
// Create a thread group field
private final ThreadGroup group = new ThreadGroup("ThreadPoolGroup");
// Create a LinkedList field containing Runnable
private final List<Runnable> tasks = new LinkedList<>();
public ThreadPool(int poolSize) {
// create several Worker threads in the thread group
for (int i = 0; i < poolSize; i++) {
var worker = new Worker(group, "worker-" + i);
worker.start();
}
}
private Runnable take() throws InterruptedException {
synchronized (tasks) {
// if the LinkedList is empty, we wait
while (tasks.isEmpty()) tasks.wait();
// remove the first job from the LinkedList and return it
return tasks.remove(0);
}
}
public void submit(Runnable job) {
// Add the job to the LinkedList and notifyAll
synchronized (tasks) {
tasks.add(job);
tasks.notifyAll();
}
}
public int getRunQueueLength() {
// return the length of the LinkedList
// remember to also synchronize!
synchronized (tasks) {
return tasks.size();
}
}
public void shutdown() {
// this should stop all threads in the group
group.interrupt();
}
private class Worker extends Thread {
public Worker(ThreadGroup group, String name) {
super(group, name);
}
public void run() {
// we run in an infinite loop:
while(true) {
// remove the next job from the linked list using take()
// we then call the run() method on the job
try {
take().run();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
}
Thread Local Variables
Java ThreadLocal
class can be used to create variables whose value can be accessible by only the same thread. So, even if two threads are executing the same code, and the code has a reference to the same ThreadLocal
variable, the two threads cannot see each other's ThreadLocal
variables.
public class Main {
public static class ThreadLocalStorage {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void setName(String name) {
threadLocal.set(name);
}
public static String getName() {
return threadLocal.get();
}
}
public static void main(String[] args) {
ThreadLocalStorage.setName("Main thread");
Runnable runnable = () -> {
ThreadLocalStorage.setName(getCurrentThreadName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread: [" + getCurrentThreadName() + "] " +
"- value: [" + ThreadLocalStorage.getName() + "]");
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
System.out.println("Main exits");
}
private static String getCurrentThreadName() {
return Thread.currentThread().getName();
}
}
If we run the code we can see that each thread has its own copy of the ThreadLocal object.
Main exits
Thread: [Thread-0] - ThreadLocal value: [Thread-0]
Thread: [Thread-1] - ThreadLocal value: [Thread-1]
Instead of each thread having its own value inside a ThreadLocal
, the InheritableThreadLocal
grants access to values to a thread and all child threads created by that thread.