What is a thread?
A thread, in general, is a set of instructions that can be executed independently as a task in a program.
What is multitasking?
When a program runs, it allocates its own resources in terms of memory and cpu time. Whether there is a single cpu on a computer or multiple, the cpu time is shared between the applications running on that computer. That means, if there are multiple programs running on the computer, a single program cannot get all of the cpu time for itself. While one of the programs is allowed to use the cpu time, the other programs are kept waiting (idle). When certain conditions are met, one of the waiting programs gets the turn. This concept is known as multitasking.
What is multithreading?
The concept of running multiple threads of execution that execute the same set of instructions is called multithreading. Threads in a multithreaded application share the resources of the computer they run on. Each thread of execution can be thought of as a separate cpu on a computer, executing a different part of a program.
Java is multithreaded and provides native libraries to start separate threads and run the same piece of code with multiple threads in parallel. Java threads are mapped to native threads of the operating system and it is the operating system that decides how many cores it would use to run a multithreaded application. For simple programs, even though there are multiple threads, the operating system’s decision would be to use a single core and let the threads take turns while using the cpu time. JVM gets its own turn with the CPU from the operating system and does its own scheduling to the threads that it manages. These threads, in turn, share the CPU time allocated to the JVM by the operating system.
In a Java program, the main() method runs in a thread named main thread. That means, every Java program has at least one thread running. Every other thread runs in parallel with main and becomes a separate thread of execution itself. In a multithreaded program, every thread of execution has its own call stack. The relationship between a thread and a call stack is one-to-one in Java. main() method is always the first method in the main thread’s call stack.
Defining and running threads
Java provides the java.lang.Thread class for writing multithreaded programs. A Java thread is basically an instance of java.lang.Thread class. java.lang.Thread class basically defines 4 methods to work with threads.
- start(): Starts a thread
- static yield(): Causes the running thread to give up its cpu share to one of the waiting threads.
- static sleep(long): causes the executing thread to pause for the given number of milliseconds
- run(): the code that is run when a thread is started.
java.lang.Thread class implements the runnable interface and provides a default, empty implementation for the run method. The behavior for a thread can be defined by extending the Thread class and overriding run() method. Because the Thread object implements Runnable, every Java thread is a Runnable.
java.lang.Thread class provides 5 basic constructors to create a thread object:
- 1- Thread(): The default no-arg constructor.
- 2- Thread(String threadName): Same as 1 but accepts an argument as the name given to the thread.
- 3- Thread(java.lang.Runnable target): Accepts a java.lang.Runnable implementation as an argument which is called a target.
- 4- Thread(java.lang.Runnable, String threadName): Same as 2 but also accepts a name given to the thread as the second argument.
- 5- Thread(Thread thread): A Thread can be created from another Thread object using this constructor but it is not used in practice.
A thread is started by calling start() on a Thread instance. When start() method is called on a thread, the thread starts running by running the code inside the run() method. The instructions inside the run() method is the code that will be run by multiple instances of the thread in parallel. What operations will be performed by the thread is determined by the code that goes inside the run() method implementation. A new thread of execution always starts with the run() method.
Java provides two ways to override the run() method to define a thread’s behavior:
- by extending java.lang.Thread class
- by implementing java.lang.Runnable interface
Once there’s a class that provides an implementation of run() method, the proper constructor can be used to initialize a Thread. A Thread object can still be initialized with the default implementation without overriding the run() method but because the run method is empty, it will not do anything once it is started. Once started, each Java thread will run until it completes. This completion is either executing all the instructions inside the run() method, or running until it is interrupted and exits unexpectedly.
Creating and Starting Threads
The easiest way to create a thread in Java is by extending java.lang.Thread class and overriding the run() method in the subclass. Because Java does not allow extending multiple classes, a class extending a Thread cannot inherit code from another class.
Let’s create a new thread by extending Thread class and start two instances of it.
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public void run() {
System.out.println(“Hello from “ + Thread.currentThread().getName());
}
public static void main(String [] args) {
MyThread thread1 = new MyThread(“thread1”);
MyThread thread2 = new MyThread(“thread2”);
thread1.start();
thread2.start();
}
}
The output would typically look like
Hello from thread1.
Hello from thread2.
The explanation of the code is as follows:
1- MyThread class extends java.lang.Thread, therefore every instance of MyClass is a Java thread.
2- We want to use the Thread constructor that accepts the thread name as an argument because we want to know which thread the output comes from. Therefore, applying principles of OOP inheritance, we create the same constructor in the class that extends Thread and call super(String name) from that constructor.
3- MyClass overrides the run() method to define its custom thread behavior. The run() method of MyClass prints “Hello from <thread name>” on the console. The thread that’s currently running is accessed using the static Thread.currentThread() method. Once the current thread is accessed, its name can be retrieved by invoking getName() method on it.
4- Inside the main() method, two instances of MyThread class are created, with names thread1 and thread2.
5- Both threads are started using start() method.
6- Both threads run until completion, which, in this case, is printing a Hello message with the thread’s name on the console.
7- Once done, both threads exit and the program flow stops.
Java provides a second way to create threads: by implementing the java.lang.Runnable interface. Now, let’s write another thread that does the same thing by implementing java.lang.Runnable.
public class MyThread implements Runnable {
public void run() {
System.out.println(“Hello from “ + Thread.currentThread().getName());
}
public static void main(String [] args) {
MyThread runnable1 = new MyThread();
Thread thread1 = new Thread(runnable1, “thread1”);
MyThread runnable2 = new MyThread();
Thread thread2 = new Thread(runnable2, “thread2”);
thread1.start();
thread2.start();
}
}
This time, MyThread class is not a Thread but it is an implementation of Runnable. Therefore, we create the threads using Thread(Runnable target, String name) constructor.
It is even possible to create a thread by implementing Runnable as an anonymous inner class and implementing the run() method inline.
public class MyClass implements Runnable {
public static void main(String [] args) {
Thread thread1 = new Thread(new Runnable() {
public void run() {
System.out.println(“Hello from “ + Thread.currentThread().getName());
}, “thread1”);
Thread thread2 = new Thread(new Runnable() {
public void run() {
System.out.println(“Hello from “ + Thread.currentThread().getName());
}, “thread2”);
thread1.start();
thread2.start();
}
}
All 3 implementations we have seen so far create threads that perform the same operations.
Because this is a multithreaded application, the output from this program is not guaranteed. Running this code a few times in a row would at some point give the following output.
Hello from thread2.
Hello from thread1.
The reason for this is that, once multiple threads are running in parallel, the execution order of the threads is not guaranteed. The execution order is determined by a part of the JVM called Thread scheduler. Once the start() method is called on a thread, the thread does not start running immediately but instead becomes eligible to run. Following this, the thread moves into the Thread Pool. The Thread Pool is the place where the threads eligible to run are kept. The Thread Scheduler picks the threads to run from Thread Pool. Threads move in and out of the Thread Pool depending on the decisions of the Thread Scheduler which is based on a number of conditions. The thread scheduler takes into account many things like system availability while deciding to pick which thread to run. Therefore no two runs of the same multithreaded Java application are guaranteed to produce the same results.
Here are the key points to keep in mind while working with threads:
1- Once a thread is started, it cannot be started again.
2- On a single CPU, at one time, only one thread stack may execute.
3- The statements inside the run method are executed sequentially but which statement from which thread is executed at one time is determined by the Thread scheduler and is not guaranteed.
Thread Life Cycle
In a Java program, a Thread object might not be executing the instructions defined in its run() method all the time. It can be in many different states. For example, a Thread can be paused, or it might be interrupted before it completed running all the statements inside the run() method. The states that a Thread can be in from its creation until it stops running is called a thread’s Life Cycle.
A thread can have 5 states in a Java program.
1- New: Once a Thread instance is created but before it is started, the thread is in new state.
2- Runnable: Once the start method is called on a thread, the thread becomes eligible to run, is added to the Thread Pool and moves into the runnable state. It is important to keep in mind that the thread does not start running until it is picked up by the Thread Scheduler to run. A thread may also go back to this state from running state or one of blocked/waiting/sleeping states.
3- Running: Once a thread is selected by the Thread Scheduler from the Thread Pool for running, it moves into the running state. This is the state where the code inside the run() method is executed.
4- Blocked/Waiting/Sleeping: When the thread becomes ineligible to run but is still eligible to run later, it moves into one of Blocked/Waiting/Sleeping states. The reason for this may be:
- Thread.sleep(long milliseconds) method is called inside the run() method. This moves the thread into sleeping state.
- wait() method is called on the thread which moves the thread into waiting state, until notify() or notifyAll() method is called on one of the running threads and the waiting thread is selected for running again.
- When the thread is blocked waiting on a resource or a lock, the thread moves into the blocked state. The thread can get out of this state and become eligible to run when the lock or the resource that the thread waits on is available.
A thread that is in one of blocked/waiting/sleeping states is alive but just not eligible to run.
5- Dead: When a thread is done executing all the statements inside its run() method, it moves into the dead state. A thread in this state is still valid but not alive and therefore it just cannot be eligible to run again. Invoking start() method on a thread in this state causes a runtime exception.
Thread safety and synchronization
In a multithreaded application, it is most of the time a requirement to keep some sections of the code available to only one thread. A section of the code that can be executed by only one thread at a time is referred to as thread-safe. For example, let’s say, in a program, we want to increment and read integers in an atomic way, meaning that no other thread can have access to the integer that we’re working on while we’re incrementing it and reading it. We can have this code in a class as follows.
public class Incrementer {
private int i;
public Incrementer(int i) {
this.i = i;
}
public int incrementAndGet(int x) {
i += x;
return i;
}
}
Now if we create two threads that have access to this class, we will see that the code inside the incrementAndGet() method is not thread-safe. Let’s say we would like to create two threads and we would expect both of them to increase the value of the instance variable by 2.
public class MyClass extends Thread {
private Incrementer incrementer;
public MyClass(Incrementer incrementer) {
this.incrementer = incrementer;
}
public void run() {
System.out.println(incrementer.increment(2));
}
public static void main(String [] args) {
int i = 3;
Incrementer incrementer = new Incrementer(i);
MyClass myClass1 = new MyClass(incrementer);
MyClass myClass2 = new MyClass(incrementer);
myClass1.start();
myClass2.start();
}
}
Here, in this example, both threads are expected to increment the i variable by 2 and print it on the console while running. Our expectation would be that the
- the first thread increments i by 2, I becomes 5 and 5 is printed on the console
- the second thread increments i by 2, I becomes 7 and 7 is printed on the console
However, after a few runs we could see the following output.
7
7
The reason for this is that, the code inside the incrementAndGet() method is not thread-safe and each statement is open to intervention from multiple threads at the same time. What actually happened was:
- the first thread incremented i by 2, i became 5
- the second thread accessed i which now has value 5 instead of 3
- the second thread incremented i by 2, i became 7
- the first thread printed the value of i on the console, which is 7
- the second thread printed the value of I on the console, which is 7
- The problem here happens because incrementAndGet method is accessed by both of the threads at the same time. Therefore, for our purpose, we need to guard this section of the code. This is done using synchronized keyword.
Before we go on, let’s take a look at the following concepts:
Critical section: In a multithreaded program, the part of the code that should not be open to multiple threads at the same time is called the critical section. The code inside the incrementAndGet() method is a critical section.
Race condition: When multiple threads access some part of the code and work towards updating it at the same time, a Race Condition occurs. The reason for this is that, the order of threads executing in a multi-threaded application is not guaranteed and it is determined by the Thread Scheduler.
What is a synchronized block?
synchronized keyword is used to protect critical sections of code from interference by multiple threads. When a block of code is surrounded by a synchronized block, only one thread can execute that block of code at a time. A synchronized block of code is defined as follows:
synchronized<intrinsic lock object instance> {
…
}
Every object in Java has an intrinsic lock. To enter a synchronized block, a thread must first obtain the intrinsic lock associated with the lock object that the block is synchronized on. Once that happens, the thread can execute the code inside the synchronized block while the other threads wait to get the lock. Once the first thread is done executing the instructions inside the synchronized block and leaves it, the lock is released. Then one of the waiting threads gets the lock and can enter the synchronized block itself.
So, changing the incrementAndGet method as follows would fix the problem.
public int incrementAndGet(int x) {
synchronized(this) {
i += x;
return i;
}
}
Now, the code inside the incrementAndGet method is synchronized on the intrinsic lock that is associated with the Incrementer class’ instance that is initialized by MyClass. This instance is shared by both of the threads. The thread accessing the synchronized block first acquires the lock. While this thread executes the code inside the synchronized block, the other thread waits. This way, the incrementAndGet operation becomes atomic.
Synchronized Methods
Synchronized methods can be accessed by only one thread at a time. When a synchronized method of an object is executed by a thread, all the other synchronized methods of the object become locked and can be accessed by none of the other threads. If any other threads were already executing a synchronized method, they are allowed to complete their execution but no new threads can execute the synchronized methods of the object until the first thread leaves the synchronized method and the lock is released.
A synchronized method is declared with the synchronized keyword. The synchronized keyword comes after the modifiers and before the return type. The method declaration format is typically as follows.
<access modifier> synchronized <return type> <method name>(<method arguments>) {…}
In our previous example, if we declared the incrementAndGet() method as synchronized, we would provide thread-safe access to the code inside it.
public synchronized int incrementAndGet(int x) {
i += x;
return i;
}
What is a thread safe class?
A class is thread safe if all the critical sections of code used directly or indirectly inside the class can be accessed by one thread at a time. The easiest way to achieve this is to use synchronized methods and blocks.
What is thread deadlock?
When two threads are blocked, waiting on each other’s locks, none of them can get the lock and they just keep waiting. This process is called thread deadlock.
Let’s consider the following class.
public class MyClass {
public static class Lock {
public boolean locked;
}
private Lock lock1 = new Lock();
private Lock lock2 = new Lock();
public boolean bothLocked1() {
synchronized(lock1) {
synchronized(lock2) {
return lock1.locked && lock2.locked;
}
}
}
public boolean bothLocked2() {
synchronized(lock2) {
synchronized(lock1) {
return lock1.locked && lock2.locked;
}
}
}
public static void main(String [] args) {
MyClass myClass = new MyClass();
final MyClass myClass = new MyClass();
myClass.lock1.locked = true;
myClass.lock2.locked = true;
new Thread(new Runnable() {
public void run() {
myClass.bothLocked1();
}
}).start();
new Thread(new Runnable() {
public void run() {
myClass.bothLocked2();
}
}).start();
}
}
MyClass class’ bothLocked1 method synchronizes first on lock1 then lock 2. bothLocked2() method synchronizes first on lock2 then on lock1. In the main method, two threads are created and started. The first thread calls bothLocked1() method while the second one calls bothLocked2() method on the same MyClass instance. Now let’s consider the following happens.
Thread 1 gets the intrinsic lock associated with lock1.
Thread 2 gets the intrinsic lock associated with lock 2.
Thread 1 begins waiting on the intrinsic lock associated with lock2.
Thread 2 begins waiting on the intrinsic lock associated with lock 1.
None of the threads can get the lock it is waiting on since none of them releases the lock they hold.
If the thread scheduler schedules the running of the threads this way, it is a deadlock.
Multithreaded code should be designed to avoid deadlock conditions. Some precautions might be
– using locks as little as possible in the code
– keeping them local if possible
– keep the code synchronized on the lock at the minimum.
Thread.sleep(long milliseconds) method
A thread’s execution may be paused by calling the static sleep() method. The signature of sleep method is as follows:
static sleep(long)
Once the sleep() method is called while a thread executes, the execution of the thread is paused for the given number of milliseconds.
sleep() method specifies to throw java.lang.InterruptedException. Therefore, when Thread.sleep() is called, java.lang.InterruptedException needs to be handled with catch/specify.
public class MyClass {
public static void main(String [] args) {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
System.out.println(i + 1);
}
}
}).start();
}
}
This code prints the numbers from 1 to 5 on the console, while waiting for a second after printing each number.
Thread priority
Every Java thread has a thread priority which is a number between 1 and 10. The Thread Scheduler uses priorities of threads while scheduling. The higher the priority of a thread is, the sooner it is likely to be scheduled for running by Thread Scheduler.
A Thread’s priority can be set using the following method.
setPriority(int priority)
Java language defines the following static final variables representing thread priorities.
Thread.MIN_PRIORITY = 1
Thread.NORM_PRIORITY = 5
Thread.MAX_PRIORITY = 10
Thread.yield() method
When it is called, it puts the current thread in runnable state and gives up its turn to the other threads that have the same priority.
join() method
join() is a non-static method. When invoked on a thread, it causes the currently running thread to be ineligible to run until the thread that invoked the join() method completes its execution.
wait/notify/notifyAll
wait(), notify() and notifyAll() methods are used by threads while interacting with each other. All these three methods can be invoked only from within a synchronized context.
wait(): When a thread calls wait on an object, it releases the instrinsic lock associated with the object. This puts the thread in a waiting condition until some other thread calls notify on the same object. Once notify is called on the same object, the thread can reobtain the lock and continue with the execution. wait() method throws InterruptedException.
notify(): When a thread calls notify on an object, it is a call to a thread waiting to obtain the lock. This call wakes up one of the threads that is waiting on the lock.
notifyAll(): This is the same as notify() except that, it wakes up all the threads waiting to obtain the lock instead of waking up one thread. Then the scheduler decides which thread should obtain the lock.
A thread can invoke wait, notify or notifyAll method on an object only if it owns the intrinsic lock associated with that object. Doing otherwise throws an IllegalMonitorStateException. Because this is a runtime exception, the design of wait/notify/notifyAll becomes really important, as the potential problems will not surface during compile time.
Now, let’s see the following example.
public class Lock {
public boolean isWaiting;
public void doWait() throws InterruptedException {
isWaiting = true;
wait();
}
}
public class MyThread1 implements Runnable {
private Lock lock;
public MyThread1(Lock lock) {
this.lock = lock;
}
public void run() {
while (true) {
if (lock.isWaiting) {
synchronized (lock) {
System.out.println(“Calling notify()”);
lock.notify();
break;
}
}
}
}
}
public class MyThread2 implements Runnable {
private Lock lock;
public MyThread2(Lock lock) {
this.lock = lock;
}
public void run() {
synchronized (lock) {
System.out.println(“Calling wait()”);
try {
lock.doWait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
lock.isWaiting = false;
}
}
}
public class MyClass {
public static void main(String[] args) {
Lock lock = new Lock();
Thread thread1 = new Thread(new MyThread1(lock));
Thread thread2 = new Thread(new MyThread2(lock));
thread1.start();
thread2.start();
}
}
Here, we first define a Lock object whose intrinsic lock is used for synchronizing threads on. The Lock object has an instance variable of boolean type named isWaiting, which is false on initialization by default. This instance variable is used for signaling to the other threads that wait() method was called on this lock. Following that, we define two thread classes which implement runnable method. Both of the threads have an instance variable of type Lock and both accept a Lock object as a constructor argument which is set to the instance variable lock on initialization. In the main method of MyClass, thread1 and thread2 are initialized with the same lock instance as an instance variable. Following that, the threads are started by calling the start() method. Once the threads are in the running stage, thread1 starts invoking the lock object’s isWaiting() in an endless loop, method which returns false. So, thread1’s run method can’t get past the if condition and get the lock object’s intrinsic lock.So it continues trying until the lock object’s isWaiting() method returns true. Meanwhile, thread2 obtains lock object’s intrinsic lock and calls doWait() method of the lock object. This method sets isWaiting field of the lock to false and invokes wait() on the lock object itself. The call to wait() causes thread2 to release the intrinsic lock associated with the lock object. Now, isWaiting() method of the lock object returns true. Because the lock is released, thread1 obtains the intrinsic lock of lock object. thread1 calls notify() method and completes running. The notify() call wakes up the waiting thread, thread2. So, thread2 goes on with the execution of the code inside its run method and completes running. The output is
Calling wait()
Calling notify()