Java Threads (original) (raw)
**Java threads are lightweight subprocesses, representing the smallest unit of execution with separate paths. The main advantage of multiple threads is efficiency (allowing multiple things at the same time). For example, in MS Word, one thread automatically formats the document while another thread is taking user input. Additionally, multithreading ensures quick response, as other threads can continue execution even if one gets stuck, keeping the application responsive.
Threads in a Shared Memory Environment in OS
As shown in the above diagram, a thread runs inside the process, and there will be context-based switching between threads. There can be multiple processes running in an OS, and each process can have multiple threads running simultaneously. The Multithreading concept is popularly applied in games, animation, etc.
Concept of Multitasking
To help users, the operating system provides users with the privilege of multitasking, where users can perform multiple actions simultaneously on the machine. This Multitasking can be enabled in two ways:
- Process-Based Multitasking
- Thread-Based Multitasking
**1. Process-Based Multitasking (Multiprocessing): In this type of multitasking, processes are heavyweight, and each process is allocated by a separate memory area and as the process is heavyweight the cost of communication between processes is high and it takes a long time for switching between processes as it involves actions such as loading, saving in registers, updating maps, lists, etc.
**2. Thread-Based Multitasking: As we discussed above, threads are provided with lightweight nature and share the same address space, and the cost of communication between threads is also low.
Life Cycle of Thread
During its lifetime, a thread transitions through several states, they are:
- New State
- Active State
- Waiting/Blocked State
- Timed Waiting State
- Terminated State
We can see the working of **different states in a Thread in the above diagram, let us know in detail each and every state:
Working of Thread States
**1. New State: By default, a thread will be in a new state, in this state, code has not yet started execution.
**2. Active State: A thread that is a new state by default gets transferred to Active state when it invokes the **start() method, this Active state contains **two sub-states namely:
- **Runnable State: In this state, The thread is ready to run at any given time and it's the job of the Thread Scheduler to provide the thread time for the runnable state preserved threads. A program that has obtained Multithreading shares slices of time intervals which are shared between threads hence, these threads run for some short span of time and wait in the runnable state to get their schedules slice of a time interval.
- **Running State: When the thread receives CPU allocated by thread scheduler, it transfers from the "Runnable" state to the "Running" state, and after the expiry of its given time slice session, it again moves back to the "Runnable" state and waits for its next time slice.
**3. Waiting/Blocked State: If a thread is inactive but on a temporary time, then either it is a waiting or blocked state, for example, if there are two threads, T1 and T2 where T1 needs to communicate to the camera and the other thread T2 already using a camera to scan then T1 waits until T2 thread completes its work, at this state T1 is parked in waiting for the state, and in another scenario, the user called two Threads T2 and T3 with the same functionality and both had same time slice given by thread scheduler then both Threads T1, T2 is in a blocked state. When there are multiple threads parked in a Blocked/Waiting state thread scheduler clears queue by rejecting unwanted threads and allocating CPU on a priority basis.
**4. Timed Waiting State: Sometimes the longer duration of waiting for threads causes starvation, if we take an example like there are two threads T1, T2 waiting for CPU and T1 is undergoing a critical coding operation and if it does not exist the CPU until its operation gets executed then T2 will be exposed to longer waiting with undetermined certainty, In order to avoid this starvation situation, we had timed waiting for the state to avoid that kind of scenario as in timed waiting, each thread has a time period for which sleep() method is invoked and after the time expires the threads starts executing its task.
**5. Terminated State: A thread will be in terminated state, due to the below reasons:
- Termination is achieved by a thread when it finishes its task normally.
- Sometimes threads may be terminated due to unusual events like segmentation faults, exceptions...etc. and such kind of termination can be called abnormal termination.
- A terminated thread means it is dead and no longer available.
Java Main Thread
As we are familiar, we create main method in each and every Java program, which acts as an entry point for the code to get executed by JVM, Similarly in this multithreading concept, each program has one main thread which was provided by default by JVM, hence whenever a program is being created in java, JVM provides the main thread for its execution.
How to Create Threads in Java?
We can create threads in java using two ways, namely :
- Extending Thread Class
- Implementing a Runnable interface
1. By Extending Thread Class
We can run threads in Java by using thread class, which provides constructors and methods for creating and performing operations on a thread, which extends a thread class that can implement runnable interface. We use the following constructors for creating the Thread:
**Example:
Java `
//Driver Code Starts{ import java.io.; import java.util.; //Driver Code Ends }
class MyThread extends Thread { // initiated run method for Thread public void run() { String str = "Thread Started Running..."; System.out.println(str); } }
//Driver Code Starts{ public class Geeks { public static void main(String args[]) { MyThread t1 = new MyThread(); t1.start(); } } //Driver Code Ends }
`
Output
Thread Started Running...
2. Using Runnable Interface
**Example:
Java `
//Driver Code Starts{ import java.io.; import java.util.; //Driver Code Ends }
class MyThread implements Runnable
{
// Method to start Thread
public void run()
{
String str = "Thread is Running Successfully";
System.out.println(str);
}
}
//Driver Code Starts{ public class Geeks { public static void main(String[] args) { MyThread g1 = new MyThread();
// initializing Thread Object
Thread t1 = new Thread(g1);
// Running Thread
t1.start();
}
} //Driver Code Ends }
`
Output
Thread is Running Successfully
Running Threads in Java
There are two methods used for running Threads in Java:
- run() Method in Java
- start() Method in Java
**Example: Running a thread using start() Method
Java `
// Running the Threads in Java import java.io.; import java.util.;
// Method 1 - Thread Class class ThreadImpl extends Thread { // Method to start Thread @Override public void run() { String str = "Thread Class Implementation Thread" + " is Running Successfully"; System.out.println(str); } }
// Method 2 - Runnable Interface
class RunnableThread implements Runnable
{
// Method to start Thread
@Override
public void run()
{
String str = "Runnable Interface Implementation Thread"
+ " is Running Successfully";
System.out.println(str);
}
}
public class Geeks { public static void main(String[] args) { // Method 1 - Thread Class ThreadImpl t1 = new ThreadImpl(); t1.start();
// Method 2 - Runnable Interface
RunnableThread g2 = new RunnableThread();
Thread t2 = new Thread(g2);
t2.start();
// Wait for both threads to finish before printing the final result
try {
// Ensures t1 finishes before proceeding
t1.join();
// Ensures t2 finishes before proceeding
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
`
Output
Thread Class Implementation Thread is Running Successfully Runnable Interface Implementation Thread is Running Successfully
The common mistake is starting a thread using run() instead of start() method.
Thread myThread = new Thread(MyRunnable());
myThread.run(); //should be start();
The run() method is not called by the thread you created. Instead, it is called by the thread that created the myThread.
**Must Read start() vs run() in Java article, to explore more about the topic.
Checking States of Thread in Java
Let us see the working of thread states by implementing them on Threads t1 and t2.
**Example:
Java `
class Geeks implements Runnable { public void run() { // implementing try-catch Block to set sleep state // for inactive thread try { Thread.sleep(102); } catch (InterruptedException i1) { i1.printStackTrace(); }
System.out.println("The state for t1 after it invoked join method() on thread t2: "
+ " " + ThreadState.t1.getState());
// implementing try-catch block
try {
Thread.sleep(202);
} catch (InterruptedException i2) {
i2.printStackTrace();
}
}
}
// Creation of ThreadState class public class ThreadState implements Runnable { // t1 static to access it in other classes public static Thread t1; public static ThreadState o1;
public void run() {
Geeks geeks = new Geeks();
Thread t2 = new Thread(geeks);
// Thread is created and its in new state
t2.start();
// Now t2 is moved to runnable state
System.out.println("State of t2 Thread, post-calling of start() method is: "
+ " " + t2.getState());
// Create a try-catch block to set t1
// in waiting state
try {
Thread.sleep(202);
}
catch (InterruptedException i2) {
i2.printStackTrace();
}
System.out.println("State of Thread t2 after invoking to method sleep() is:"
+ " " + t2.getState());
try {
t2.join();
System.out.println("State of Thread t2 after join() is: " + t2.getState());
}
catch (InterruptedException i3) {
i3.printStackTrace();
}
System.out.println("State of Thread t1 after completing the execution is: "
+ " " + t1.getState());
}
public static void main(String args[]){
o1 = new ThreadState();
t1 = new Thread(o1);
System.out.println("Post-spanning, state of t1 is: " + t1.getState());
// lets invoke start() method on t1
t1.start();
// Now, Thread t1 is moved to runnable state
System.out.println("Post invoking of start() method, state of t1 is: "
+ " " + t1.getState());
}
}
`
**Output:
Concurrency Problems
- Race Condition: Occurs when multiple threads access shared data simultaneously, leading to inconsistent results. It happens when the code is not thread-safe.
- Deadlock: Happens when two or more threads are blocked forever, each waiting for the other to release a lock.
- Livelock: Threads are active but unable to make progress because they keep responding to each other in an endless loop.
- **Thread Starvation: A thread is perpetually denied access to resources because other threads are given priority.
- Priority Inversion: Occurs when a low-priority thread holds a lock needed by a high-priority thread, blocking its progress.
**Solution of the Problem: Synchronization , Locks , Atomic Variables , Thread-safe Collections , Avoiding Deadlocks , Thread Pools and Using volatile Keyword.
These are the points we can use to avoid concurrency problems in our program or application.
Let us see how we can avoid it.
Java `
// Concurrent Problems Solution class Counter {
// Shared resource
private int count = 0;
// Synchronized method to ensure
// thread-safe increment
public synchronized void increment()
{
// Increment the counter
count++;
}
// Method to get the current count
public int getCount() { return count; }
}
class CounterThread extends Thread { private Counter counter;
// Constructor to initialize the
// counter
public CounterThread(Counter counter)
{
this.counter = counter;
}
// Override the run method to
// increment the counter
@Override public void run()
{
System.out.println("Running the Thread");
// Increment the counter
for (int i = 0; i < 1000; i++) {
counter.increment();
}
}
}
public class Geeks { public static void main(String[] args) {
// Create a shared Counter object
Counter counter = new Counter();
// Create multiple threads that will
// increment the counter
Thread t1 = new CounterThread(counter);
Thread t2 = new CounterThread(counter);
Thread t3 = new CounterThread(counter);
// Start the threads
t1.start();
t2.start();
t3.start();
// Wait for all threads to finish
try {
t1.join();
t2.join();
t3.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
// Print the final count
System.out.println("Final count: "
+ counter.getCount());
}
}
`
Output
Running the Thread Running the Thread Running the Thread Final count: 3000
Explanation of the above Program:
- **Counter Class:
- Contains a private integer count as the shared resource.
- The increment method is synchronized, ensuring that only one thread can execute it at a time, preventing concurrent modifications.
- **CounterThread Class:
- Extends thread and is designed to increment the shared counter object.
- The run method increments the counter 1000 times.
- **Main Class:
- Creates a single counter instance shared among three CounterThread instances.
- Starts the threads and waits for them to finish using join.
- Prints the final count, which should be 3000 (1000 increments from each of the three threads).
Advantages of Creating Threads
- When compared to processes, Java threads are more lightweight, it takes less time and resources to create a thread.
- Threads share the data and code of their parent process.
- Thread communication is simpler than process communication.
- Context switching between threads is usually cheaper than switching between processes.