Java | Multithreading Part 3: Threads

MrAndroid
15 min readDec 11, 2022

--

In this article, we will talk about Threads.

When a Java application starts, a process is created.

Process is an instance of a program at runtime, an independent entity that is allocated system resources (such as CPU time and memory).

Each process runs in a separate address space: one process can’t access the variables and data structures of another. If a process wants to access other processes resources, it must use inter-process communication.

For each process, the OS creates a so-called “virtual address space” to which the process has direct access. This space belongs to the process, contains only its data and is at its complete disposal.

The operating system, on the other hand, is responsible for how the process’s virtual space is projected onto physical memory.

Thread — a definite way of executing a process, which determines the sequence of execution of code in the process.

Threads are always created in the context of a process, and their entire life passes only within its boundaries.

When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class).

The JVM allows an application to have multiple threads of execution running concurrently.

Thread in Java represented as an instance of a class java.lang.Thread. java.lang.Thread in Java is not itself a thread. This is just a kind of API for low-level threads that are managed by the JVM and the operating system.

Architecture of Thread:

A thread can be in one of the following states:

  • NEW — A thread that has not yet started is in this state..
  • RUNNABLE — This state is for a thread running in the JVM.
  • BLOCKED — A thread that is blocked waiting for a monitor lock is in this state.
  • WAITING — A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
  • TIMED WAITING — A thread lies in a timed waiting state when it calls a method with a time-out parameter. A thread lies in this state until the timeout is completed or until a notification is received.
  • TERMINATED — The terminated thread is in this state.
Thread states

When we create a thread object by use of the new operator, the thread enters in NEW state. In this state, the thread is not considered alive.

ThreadA threadA = new ThreadA();

The thread remains in the NEW state until the JVM calls the start() method. As soon as the start() method is called, the thread leaves the NEW state and enters the RUNNABLE state.

ThreadA threadA = new ThreadA();
threadA.start();

A thread can’t move back to the NEW state after the call of the start() method.

After calling start() method. The control transfers to the Thread scheduler to finish its execution. Whether to run this thread instantly or keep it in runnable thread pool before running, depends on the OS implementation of thread scheduler.

Thread scheduler in Java is the component of JVM that determines the execution order of multiple threads on a single processor (CPU).

The thread scheduler allocates a fixed CPU amount of time to each individual thread and then pauses so that other threads can get a chance to run.

In the RUNNABLE state a thread can be either RUNNING or READY_TO_RUN .

The RUNNING state doesn’t exist in reality, but it’s considered a part of the RUNNABLE state. RUNNING is the state in which the CPU executes the code of the thread. And READY_TO_RUN is the state in witch the Thread is waiting time of processor for run code.

We understand what happens to a thread when it is RUNNING, but it can also be BLOCKED or wait.

In the waiting mode, the thread can be indefinitely, in which case the state will be WAITING or it can be for some time, in which case the state will be TIMED WAITING.

A RUNNABLE thread can move to the TIMED WAITING state for a specific time interval.

When the thread is in state TIMED WAITING, this means that the thread is in this state for a certain period of time. It will remain in this state until the specified time interval has elapsed or a notification has been received.

We have some methods that are used to move the thread in the TIMED WAITING state like sleep(time), wait(timeout), join(timeout), parkNanos(), parkUntil().

For example we haveThreadA witch make some job.

public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
threadA.start();

threadA.join(250L);
System.out.println("ThreadA finished work= " + !threadA.isAlive());
}


class ThreadA extends Thread {
@Override
public void run() {
try {
sleep(5000L);
} catch (InterruptedException e) {
return; // Thread termination after interrupt
}
}
}

The join() method allows the current thread to “suspend” the execution of its code and “skip” forward another thread.

In main method we wait 250 milliseconds and continue work.

Let’s imagine a situation in which we have a thread BestCurrencyThread that calculates the best currency rate. And the thread that is updates the currencies is RefreshCurrencyThread.

Every time we want to get the best rate, our BestCurrencyThread should try to update the currency rates, but since this can be a long operation and we can wait no more than 250 milliseconds, we call the join() method with timeout.

If we didn’t have time to get the result within 250 milliseconds, we continue updating but use the previous result.

class BestCurrencyThread extends Thread {
private final RefreshCurrencyThread refreshThread;

public BestCurrencyThread(RefreshCurrencyThread refreshThread) {
super();
this.refreshThread = refreshThread;
}

@Override
public void run() {
refreshThread.start();
try {
refreshThread.join(250L);
} catch (InterruptedException e) {
// ignored
}
calculateBestRate();
}

private void calculateBestRate() {
// todo calculate
}
}

class RefreshCurrencyThread extends Thread {
@Override
public synchronized void start() {
super.start();
try {
Thread.sleep(5000L);
// todo refresh currencies
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

WAITING state:

When a thread is in the WAITING state, it means some other threads are on priority. A thread can be in the state for various reasons. We can put a thread in the WAITING state by calling its wait() method, join() method or park() method.

BLOCKED state:

When a thread is in the BLOCKED state it is not eligible to run. But thread will to be alive.

Thread can move to the BLOCKED state if thread is try to run a synchronized block but there is another thread currently running inside a synchronized block on the same object.

For example:

public static void main(String[] args) throws InterruptedException {
Work work = new Work();
Thread threadA = new ThreadA(work);
Thread threadB = new ThreadB(work);
threadA.start();
threadB.start();

Thread.sleep(1000L);
System.out.println(threadA.getName() + " state= " + threadA.getState().name());
System.out.println(threadB.getName() + " state= " + threadB.getState().name());
}

class Work {
synchronized void doWork() throws InterruptedException {
Thread.sleep(5000L);
}
}

class ThreadA extends Thread {

private final Work work;

public ThreadA(Work work) {
super("ThreadA");
this.work = work;
}

@Override
public void run() {
System.out.println(this.getName() + " start work");
try {
work.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

System.out.println(this.getName() + " finish work");
}
}

class ThreadB extends Thread {

private final Work work;

public ThreadB(Work work) {
super("ThreadB");
this.work = work;
}

@Override
public void run() {
System.out.println(this.getName() + " start work");
try {
work.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

System.out.println(this.getName() + " finish work");
}

When starting our ThreadA code, thread call the doWork() method and waits 5 seconds for this method to complete. At the same time, ThreadB calls the doWork() method of the same object. Since the Work object is already occupied by ThreadA, our ThreadB enters the BLOCKED state.

So we understood that when we create a new thread, its initial state is NEW.

After starting the thread by calling the start() method, the state changes to RUNNABLE .

When a thread terminates, interrupt, or crash, its state changes to TERMINATED .

While a thread is executing, it can have one of the following states: WAITING , BLOCKED или TIMED_WAITING .

If a thread called a synchronized block of code, then its state goes to BLOCKED.

If the thread calls one of the methods: wait(), sleep(), join() , then it goes into the WAITING state. To return to the RUNNABLE state, a thread must wait until some thread calls the notify() or notifyAll() method.

For the TIMED_WAITING state, the situation is the same as for WAITING, but the methods that bring the thread into the TIMED_WAITING state are different. These methods are: sleep(time) , wait(timeout), join(timeout), parkNanos(), parkUntil().

Once again with examples:

Our ThreadA calls the doWork() method.

class ThreadA extends Thread {

private final Work work;

public ThreadA(Work work) {
super("ThreadA");
this.work = work;
}

@Override
public void run() {
System.out.println(this.getName() + " start work");
try {
work.doWork();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

System.out.println(this.getName() + " finish work");
}
}

If any of the threads called the doWork() method before our ThreadA thread, then ThreadA is state will be BLOCKED until the doWork() method has completed its work and our ThreadA thread can start executing the doWork() method.

Now let’s see how ThreadA can change state to the WAITING state.

Let’s imagine that our thread ThreadA needs to wait for the execution of ThreadB at some point in time and only then continue working.

threadB.join();

The join() method allows the current thread to “pause” the execution of its code and “skip” forward another thread.

When we called the join() method on a ThreadB, ThreadA changed state to the WAITING state.

start() and run() method:

In the examples above, we start the thread using the start() method, but the logic of the work is in the run() method.

Method run() is the body of the thread that executes when JVM calls the start() method.

We have two ways to create a thread:

First, create new class and extends from class java.lang.Thread.

class ThreadB extends Thread {
@Override
public void run() {
// todo do something
}
}

Don’t forget about overriding run() method for to do logic in our thread.

Second,it create a new thread and provided the Runnable interface in the thread’s constructor.

Thread thread = new Thread(new Runnable() {
@Override
public void run() {

}
});
thread.start();

Runnable is an interface that represents an abstraction over a running task.

In addition to helping to solve the problem of multiple inheritance, Runnable is a clear advantage of using it is that it allows you to logically separate the execution logic of a task from direct flow control.

How does the start() method work?

Let’s look to start() method:

public synchronized void start() {

if (threadStatus != 0)
throw new IllegalThreadStateException();

group.add(this);

boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}

We see that the start() method is synchronized, only one thread can start any thread at the current time.

Method start() move thread from NEW state to RUNNABLE .

Calling the start() method twice on the same Thread object will result in an IllegalThreadStateException being thrown at runtime.

Also in method start() add the current thread to the ThreadGroup.

Threads can form a thread hierarchy by being added to a ThreadGroup. Threads belonging to a group can be controlled at the same time — we can interrupt the work of all the threads of the group at once, or set a single maximum execution priority value for them.

What if we want to stop the execution of the thread?

There is no method in Java that would force a thread to stop.

Java has a built-in thread notification mechanism called Interruption.

Interruption signals that the thread should terminate its work. We cannot terminate the thread immediately. Interruption asks the thread to finish gracefully.

You can use the Thread — interrupt() class method to correctly notify you that a thread has stopped.

This method sets some internal interrupt status flag. Later, the state of this flag can be checked using the isInterrupted() or Thread.interrupted() method.

Let’s look for example:

public static void main(String[] args) throws InterruptedException {
Work work = new Work();
Thread threadA = new Thread(work);
threadA.start();

if (!threadA.isInterrupted()) {
threadA.interrupt();
}
System.out.println(threadA.getName() + " isInterrupted= " + threadA.isInterrupted());
}

// ThreadA isInterrupted= true

We start ThreadA, then we check whether there was an interruption of the current thread, and if not, then we call the interrupt() method.

The interrupt() method is also capable of waking a thread from a waiting or sleep state. Those, if a thread has called sleep() or wait() — the current state will be interrupted and an InterruptedException eception will be thrown.

It is important to understand that interrupt is addressed not only to the code that is running on the thread, but also to the owner of the thread.

@Override
public void run() {
try {
work.doWork();
} catch (InterruptedException e) {

}
System.out.println(this.getName() + " isInterrupted= " + isInterrupted());
}

// ThreadA isInterrupted= false

When we get an InterruptedException error, the isInterrupted flag is cleared.

The correct code will look like this:

@Override
public void run() {
try {
work.doWork();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(this.getName() + " isInterrupted= " + isInterrupted());
}

// ThreadA isInterrupted= true

When receiving an InterruptedException error, we must set the isInterrupted flag to true so that we can further check that the thread needs to be suspended and be able to properly finish executing its work.

A small summary of Interruption:

It should be understood that when one thread calls the interrupt() method on another thread, this does not stop the thread, but only notifies that it should complete its work.

public static void main(String[] args) throws InterruptedException {
...

threadA.interrupt();
}

In the example above, on thread main, it is calling the interrupt() method on the ThreadA thread. For the main thread, it turns out that ThreadA has completed its work, but ThreadA only received a notification that it would not be bad to complete the work and must decide how to complete it.

In turn, if we called the interrupt() method for the ThreadA thread at the time when it called the sleep(), join(), wait() methods, then the thread receives an InterruptedException error. ThreadA must handle this error and set the is Interrupted flag to true by calling Thread.currentThread().interrupt();

Further inside the thread, we can call the isInterrupted() method to check if our thread should be terminated.

If a thread performs I/O operations and call the interrupt() method on it, then this will not stop its execution.

The solutions here differ depending on the type of data source.

If reading is coming from a file, a long-term lock is extremely unlikely, and then you can just wait for the read() method to exit.

If the read is somehow connected to the network, you should use non-blocking I/O from Java NIO.

NIO has such a nice feature that it actually takes thread interrupts into account. If we try to read or write to the channel after the thread is interrupted, thread will get a ClosedByInterruptException .

Method stop():

Thread has a stop() method, why not call it?

When a thread is forced to stop, the stop() method interrupts the thread at a non-deterministic execution location, as a result, it becomes completely unclear what to do with the resources that belong to it.

The thread can open a network connection — what to do with the data that has not yet been subtracted?

Where is the guarantee that after a further launch of the thread, he will be able to finish reading them?

If a thread has locked a shared resource, then how to release this lock and won’t forced release lead to system consistency violation? The same can be extended to the case of a database connection: if the thread is stopped in the middle of a transaction, then who will close this transaction?

Uncaught Exceptions:

If an exception occurs inside a thread that is not handled, then such a thread “dies”.

Let’s consider a situation when our thread received Exception but we throw an exception above.

We start the thread ThreadA and while the thread is in the waiting state, we interrupt it.

public class ProgramRunTest {

public static void main(String[] args) throws InterruptedException {
Work work = new Work();
Thread threadA = new ThreadA(work);
threadA.start();

if (!threadA.isInterrupted()) {
threadA.interrupt();
}
}
}

class Work {
synchronized void doWork() throws InterruptedException {
Thread.sleep(5000L);
}
}

class ThreadA extends Thread {

private final Work work;

public ThreadA(Work work) {
super("ThreadA");
this.work = work;
}

@Override
public void run() {
try {
work.doWork(); // throw InterruptedException
} catch (InterruptedException e) {
throw new RuntimeException("error message");
}
}
}

In the console we can see that our program ended with the message:
Exception in thread “ThreadA” java.lang.RuntimeException: error message
at run.ThreadA.run(ProgramRunTest.java:37)
.

We can handle exceptions to take some action for example close connection with Database and finished work of thread.

First what we can to handle exceptions, we can use a block: try catch finally .

Second, it's UncaughtExceptionHandler .

Uncaught Exception Handler:

If a uncaught exception handler is installed, it will take control.

Let's create ExceptionHandler :

class ExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread thread, Throwable t) {
System.err.println((thread.getName() + "error= " + t.getMessage()));
// ... Handle the exception
}
}

And add our example with handle error.

public static void main(String[] args) throws InterruptedException {
Thread.UncaughtExceptionHandler handler = new ExceptionHandler();

Work work = new Work();
Thread threadA = new ThreadA(work);
threadA.setUncaughtExceptionHandler(handler);
threadA.start();

if (!threadA.isInterrupted()) {
threadA.interrupt();
}
}

Our program don’t crash and continue its work as the exception will be handled in the ExceptionHandler .

More options for exception handling are provided by ThreadPoolExecutor and Feature.

These classes will be covered in the following parts.

What is thread priority?

Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority.

In theory, high priority threads get more CPU time than low priority threads. In practice, the amount of CPU time a thread receives often depends on several factors besides its priority.

To set the priority of a thread, use the class method Thread: final void setPriority(int level).

The value of level ranges from Thread.MIN_PRIORITY = 1 toThread.MAX_PRIORITY = 10.

Default Priority:Thread.NORM_PRlORITY = 5.

You can get the current thread priority value by calling the method:final int getPriority() in thread.

What are daemon threads?

Daemon threads run in the background with the program, but are not an integral part of the program.

Daemon threads are low-priority threads whose only role is to provide services to user threads.

Daemon thread can be started by calling the setDaemon(boolean value) method on the thread before it starts.

daemonThread.setDaemon(true);

The boolean isDaemon() method allows you to determine whether the specified thread is a daemon or not.

daemonThread.isDaemon()

When all user threads have finished, the JVM exits. Daemons do not perform independent tasks, so they do not prevent stopping, the program ends without waiting for their work to finish.

A daemon thread can be useful for things like cache invalidation, periodically refreshing values from external sources, and freeing up unused user resources.

ThreadLocal:

ThreadLocal — is such a type for a field bound to a thread. That is, each thread has its own field. Even if this field is a static field, it will still be different for each thread.

ThreadLocal allows having one variable to have a different value for each of the threads.

public class ThreadLocalRunnable implements Runnable {

private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

@Override
public void run() {
threadLocal.set( (int) (Math.random() * 100D) );

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

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

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

Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);

thread1.start();
thread2.start();

thread1.join(); //wait for thread 1 to terminate
thread2.join(); //wait for thread 2 to terminate
}

We can't use ThreadLocal with ExecutorService.

If we want to use an ExecutorService and pass a Runnable to it, using ThreadLocal will give non-deterministic results because we have no guarantee that each Runnable action for a given method will be handled by the same thread every time it is executed.
Because of this, our ThreadLocal will be shared between different threads. Therefore, we should not use TheadLocal along with ExecutorService.

It follows from this that ThreadLocal can only be used when we have full control over which thread chooses to execute on itself.

Conclusions:

Threads are very useful whenever a process has multiple tasks to perform independently of the others.

Thread — a certain way of executing a process, which determines the sequence of execution of code in the process.

The class Thread contains several methods for managing threads:

  • start() — starts a thread
  • run() — method to execute thread code
  • getName() — return name of thread
  • getPriority() — return priority of thread
  • isAlive() —determine if a thread is running
  • join() — allows the current thread to “suspend” the execution of its code and “skip” forward another thread.
  • sleep() — pause a thread for a given time

Working with threads looks pretty simple and obvious until we start communication between threads.

--

--