Threads are used to perform tasks in parallel. They allow concurrent operations within an application. It is a lightweight process that runs independently but shares the same resources as the CPU and memory of the parent process. The availability of threads concept gives rise to multithreading, a technique that enables multiple threads to be active within a single application, providing the capability of simultaneous execution of different code segments. This approach significantly enhances application performance and responsiveness, particularly in use cases requiring high levels of concurrency and low latency. Using threads, cpus can be effectively utilized by minimizing the idle times.
Using threads, programs can do multi-tasking and thereby do many things at the same time. Java has a evolved threading model from the beginning of its evolution. Java threads have evolved into more user-friendly versions like the Executor framework. But the fundamental concepts of Java threads remain the same. Threads are more powerful when the program runs on a machine with multiple CPUs or CPU cores.

Role of JVM
The central actor is JVM ( Java Virtual Machine), which manages the lifecycle of threads. JVM manages the state of the threads, its scheduling, and tracking. JVM provides the capability to use threads no matter which operating system the program is deployed on. Additionally, it abstracts much of the complexity involved with thread management, allowing developers to focus on higher-level programming concerns while ensuring efficient thread execution.
Creating Threads
In Java, threads can be created in different ways. The two options available are extending the Thread class or implementing the Runnable interface. Which approach you want to use depends on the use case.
Example of Extending thread
class ThreadTest extends Thread {
@Override
public void run() {
System.out.println("thread");
}
}
Code language: JavaScript (javascript)
Example of implementing the Runnable
Thread thread2 = new Thread(() -> {
System.out.println("thread 2"); });
Code language: JavaScript (javascript)
You can also create a thread like this: Thread thread = new Thread();
This is not very useful as it only creates a thread with its default run method. Not useful.
Managing Threads
Key methods are.
- start() – Used to initiate the thread.
- sleep(milliseconds) – Pauses the execution of a thread for the specified duration.
- join() – Waits for the thread to die before proceeding.
- interrupt() – Interrupts a thread, signaling it to stop its current operation.
- yield() – signals to the scheduler that the current thread is willing to relinquish its use of the CPU.
The most commonly used methods of Thread in Java, are run(), start(), and yield(). The code that you want to execute in the thread should be defined in the run method. To tell the JVM that this thread is ready for execution, invoke the start() method.
You run the thread by calling its start() method. If there are CPU cycles available, the thread will start its processing logic defined in the run method. Many developers mistakenly invoke the run() method directly. Don’t do this; instead, call the start() method.
Threads offer powerful capabilities in Java, enhancing performance and responsiveness across applications. By understanding the above methods for creating and managing threads, developers can effectively design beautiful multithreaded applications in Java.
Thead Communication and Coordination
To facilitate the communication and coordination of execution between threads, Java provides the join() and yield() methods. When a thread wants to execute portions of its code after another thread has finished its task, it can do so by invoking the join() method. Consider the example below. Thread2 waits for Thread1 to complete, and then it executes its code.
Thread Coordination Example – Join
static class ThreadCoordination {
public void test() {
Thread thread1 = new Thread(() -> {
System.out.println("thread 1");
for (int i = 0; i < 100; i++)
System.out.println("thread 1 " + i);
});
Thread thread2 = new Thread(() -> {
System.out.println("thread 2");
try {
thread1.join();
System.out.println("Thread 2 proceeding "+
"after thread 1 finishes");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread2.start();
thread1.start();
}
}
Code language: JavaScript (javascript)
The out will be as follows
thread 2
thread 1
thread 1 0
thread 1 1
thread 1 2
thread 1 3
thread 1 4
Thread 2 proceeding after thread 1 finishes
The part of the code after the call to join() gets executed only after thread1 finishes it’s execution. This way threads can coordinate executions among themselves with ease.
Synchronization and Thread Safety
Synchronization is a vital concept in Java programming, particularly when dealing with multiple threads that access shared resources. Shared resources can be multiple threads accessing the same data, the same file, or any other resource.
You should code the application in such a way that it functions correctly during simultaneous execution by multiple threads. Without proper synchronization, race conditions can occur, leading to unpredictable behavior and data inconsistency. Such a scenario can result in data loss, data corruption, and inconsistent application behavior. These issues are extremely difficult to debug. Hence, during the development of the multi-threaded application, you should handle the thread coordination upfront.
To address these issues, Java provides several synchronization mechanisms. The most common method is using synchronized methods and blocks.
If you declare a method as synchronized, only one thread can execute it at any given time for the object’s instance. This prevents concurrent access and ensures that a thread completing its execution can leave the object’s state in a consistent manner.
If you only want to prevent access to portions of code inside the method, use a synchronized block to isolate that portion of code.
Thread
ExecutorService
Thread management is a lot easier if you use the executor framework. Executor framework provides abstraction to work with a pool of threads and its management. Let us look at the Executor framework in detail. The core classes are ExecutorService and Executor.
ExecutorService: Example
package com.thestembook.java;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class ExecutorFrameworkSample {
public void test() {
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
});
}
}
Code language: JavaScript (javascript)
Conclusion
- Threads in Java offer parallel processing capability by leveraging the CPU power effectively
- Reduces the idle CPU times
- Provides capabilities for efficient coordination between threads
- Availability of abstractions like the ExecutorService offers capabilities of pooled threads are easier thread management capabilities
Leave a Reply