Threads in Java
What is Thread
Thread is the path of execution in application. Applications can have multiple threads which are executed in parallel. In Java, threads are instances of java.lang.Thread
class or subclasses which extend the Thread class. Every thread has priority (min. 1, max. 10) which is used by Thread Scheduler to determine the priority of execution. Also, a new created thread has an initial priority set from the creating thread (parent thread).
The main thread is a single non-daemon thread that is started by JVM to execute the main method. Default priority of the main thread is 5.
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
System.out.println("Name: " + mainThread.getName());
System.out.println("Priority: " + mainThread.getPriority());
System.out.println("Group: " + mainThread.getThreadGroup().getName());
}
Console output:
Name: main
Priority: 5
Group: main
Each thread in a Java creates its own stack. Size of the stack by default is 512KB to 1024KB and it can be changed using JVM command -Xss.
What is difference between process and thread
Thread is a segment of the process, a single process can have multiple threads. Threads are lightweight and they are run in shared memory space inside the process which provides easier inter thread communication between threads.
Thread and Runnable in Java
Thread
class implements a Runnable
interface which has only a run
method. The Runnable's run method should be implemented in order to be executed by thread.
The following code shows one of the ways Runnable
interface can be implemented and executed by the thread.
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i: " + i);
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread states
Java Thread.State has six states: NEW, RUNNING, BLOCKED, WAITING, TIMED_WAITING and TERMINATED.
- NEW — Thread has been created but it is not started
- RUNNING — Thread is executing in JVM
- BLOCKED — Thread is being blocked and waiting for monitor to continue execution
- WAITING — Thread is waiting indefinitely for another thread to perform action
- TIMED_WAITING — Similar as waiting state but in this case waiting time is specified
- TERMINATED — Thread has been exited
All those states are part of the Thread.State Enum class. Thread state can be checked by calling thread.getState()
methods.
Race Condition
A race condition occurs when two or more threads access shared data and try to read and write at the same time.
The following code example shows race condition
public static void main(String[] args) {
Runnable runnable = new Runnable() {
private int counter = 0;
@Override
public void run() {
while (true) {
counter++;
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ", value: " + counter);
counter--;
System.out.println(threadName + ", value: " + counter);
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
};
Thread threadA = new Thread(runnable);
threadA.setName("Thread A");
threadA.setDaemon(false);
threadA.setPriority(5);
Thread threadB = new Thread(runnable);
threadB.setName("Thread B");
threadB.setDaemon(false);
threadB.setPriority(5);
threadA.start();
threadB.start();
}
Both threads execute the same instance of Runnable
and change int variable counter
. Since there is no synchronize block, output of this code looks like:
Console output:
Thread A, value: 0
Thread B, value: 1
Thread B, value: 0
Thread A, value: 1
Thread A, value: -1
Thread B, value: 0
Thread B, value: -1
Thread A, value: 0
Thread A, value: -2
We can fix race condition by adding synchronization block:
public void run() {
while (true) {
synchronized (this) {
counter++;
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ", value: " + value);
counter--;
System.out.println(threadName + ", value: " + value);
}
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
So, I put a synchronized
block and lock it by using a monitor from an instance of Runnable
. Since both threads use the same instance of Runnable
this monitor will prevent my threads A and B from accessing the block of code at the same time.
Output of the fixed code:
Console output:
Thread A, value: 1
Thread A, value: 0
Thread B, value: 1
Thread B, value: 0
Thread A, value: 1
Thread A, value: 0
If you are interested in reading about synchronization in Singleton pattern, take a look at Java Singleton Pattern and Synchronization
What is Deadlock
The deadlock situation occurs when the first thread holds a key needed by the second thread and the second thread holds a key needed by the first thread.
The good thing is that JVM can detect deadlocks and log the information.
Example of deadlock:
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized (lock1) {
System.out.println("first lock is called");
synchronized (lock2) {
System.out.println("second lock is called");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (lock2) {
System.out.println("second lock is called");
synchronized (lock1) {
System.out.println("first lock is called");
}
}
});
threadA.start();
threadB.start();
}
Right way to stop the thread
Thread is automatically stopped when the run method has completed, but sometimes we need to stop/cancel it before the run
method ends. The right way to stop the thread is by calling the interrupt
method and checking from time to time what is the result of the isInterrupted
.
Methods suspend(), resume() and stop() are deprecated and it is not recommended to use them.
Originally published at https://dev4.dev on May 25, 2020.