Understand Threads and Threading in Java

Concurrent programming with Java and the JVM

Uxío García Andrade
Better Programming

--

Photo by drmakete lab on Unsplash.

When writing complex applications, it is common to find ourselves in need of executing multiple actions at the same time. Thankfully, Java and the JVM have been designed to support concurrent programming, as all execution takes place in the context of threads.

JVM Support for Multi-Threading

As Java was created in the ‘90s, a time when computers already had multiple processing units, their creators decided to include native support for multi-threading.

Java runs in the Java Virtual Machine. When a program is started, the JVM creates the main thread that executes the main() method. From this point on, the program can create as many threads as needed, following a hierarchical structure. In Java, a thread can be in any of the following states:

  • New: When a new thread is instantiated, it is in this state. While being in this state, the thread hasn’t yet started to be executed.
  • Runnable: A thread in this state can either be running or just be prepared to run at any point.
  • Blocked/Waiting: In this state, the thread is inactive, waiting for a resource.
  • Suspended: A thread is in this state when it calls a method with a timeout parameter, which will suspend momentarily. After that, it can resume its execution at the point where it had stopped.
  • Terminated: When a thread finishes the execution of its run() method, it is in the terminated state. The garbage collector will free the resources used by this thread.

Moreover, the JVM assigns to each thread its own method-call stack, which allows these threads to track local variables, parameters the JVM passes to a method, and the method’s return value.

The Thread Class

The first way of creating a thread in Java is to create a class that extends the Thread class, override the run method with the code the thread must run, and then instantiate this class:

Once we have this class created, we can instantiate it and start the thread:

MyThread thread = new MyThread();thread.start();

First, we allocate a new MyThread object and then we invoke the start method so that the JVM calls the run method of that thread. Note that starting a thread more than once is strictly forbidden (a thread may not be restarted once it has completed execution). If we wanted to run that thread again, we should create a new instance of the thread object.

The Runnable Interface

The runnable interface should be implemented by any class whose instances are intended to be executed by a thread. That class must define a method of no arguments called run. Furthermore, the runnable interface provides the means for a class to be active while not subclassing Thread.

In general, this interface is intended to be used when we just want to override the run method of a thread without using any other Thread method.

How to Run a Thread Multiple Times

If we want to run a thread multiple times, how would we do this in Java? One may be inclined to try the following approach:

But as I said before, this is not possible. If we try to run this code, an IllegalThreadStateException will be thrown.

In order to run it multiple times, a new thread should be instantiated. One of the possible approaches would be to have an array of threads. In this example, I will run ten threads:

Critical Section

In Java, the Object class provides a lock that allows the programmer to manage exclusive access to the critical section. Locks admit two operations: open and close. When a lock is closed, any other attempt to close it will be suspended until it is opened by the thread that had closed it. The Java way of managing these operations is by using the synchronized construct. The first way of using this construct is as follows:

The other way of using the synchronized construct is to define a synchronized method:

public class MyClass{     public synchronized int methodOne(args){         ……     }
}

Synchronizing Threads

The Thread class implements several methods that allow us to control when and how threads wait for other threads.

The first method is join(). This method is called by a thread using another thread’s reference when it wants to wait for that thread to terminate. This method throws an InterruptedException when the thread that it is waiting for is interrupted by the interrupted() method.

In the following example, the main thread would wait for the thread1 thread to terminate:

Another important method is wait(). This method should be called inside a synchronized method. If the thread that executes the wait method is interrupted, it throws an InterruptedException. The JVM adds the thread that executes the wait method to a waiting list associated with the object that makes the call. Then, the lock associated with the object is opened and it will be closed when returning from the call.

The last method we will cover is notify(). When called, it will activate a thread that is waiting on the waiting list. The thread that will be started is not determined, but it must wait until it can close the lock again. A similar method is notifyAll(), which activates all the threads on the waiting list.

--

--