BhauAutomation

Java Threads

In Java, a Thread is a lightweight process that allows concurrent execution of tasks. Multithreading improves performance by executing multiple parts of a program simultaneously, making applications more responsive and efficient.

📘 Topic: Core Java / Concurrency
Read time: 8 min
📊 Level: Intermediate
🧵 Focus: Multithreading
📖 Overview

What are Java Threads?

A Thread in Java is a single unit of execution within a process. It helps execute multiple tasks concurrently. Java provides in-built support for multithreading using the Thread class and the Runnable interface. Every Java program has at least one thread (the main thread), and additional threads can be created for parallel execution.

Two Ways to Create a Thread in Java:

1. Extending Thread class

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getId() + " is running...");
    }
    
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();  // starts the thread
        t2.start();
    }
}

2. Implementing Runnable interface

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable thread " + Thread.currentThread().getId() + " is running...");
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        Thread t2 = new Thread(new MyRunnable());
        t1.start();
        t2.start();
    }
}
🎯 Key Points

🎯 Objectives

Perform multiple tasks simultaneously

Utilize CPU efficiently by sharing resources

Improve overall application performance

Create responsive user interfaces (background tasks)

✅ Advantages

Better CPU utilization and faster task execution

Improved application responsiveness

Resource sharing between threads

Simplifies modeling of concurrent systems

⚠️ Limitations

Increased programming complexity

Can cause deadlocks, race conditions, and synchronization issues

Difficult debugging and testing

Thread overhead for large number of threads

🔄 Lifecycle

Java Thread Lifecycle States

A thread passes through several stages during its lifetime:

1. New Thread object created but not started
2. Runnable Thread is ready and waiting for CPU time
3. Running Thread is currently executing
4. Blocked/Waiting Thread is temporarily inactive (waiting for monitor lock or sleep)
5. Terminated Thread has completed its execution

Thread Lifecycle Demonstration

class LifeCycleDemo extends Thread {
    public void run() {
        System.out.println("Thread state: Running");
        try {
            Thread.sleep(1000); // moves to Waiting state
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread finishing execution");
    }

    public static void main(String[] args) throws InterruptedException {
        LifeCycleDemo t1 = new LifeCycleDemo();
        System.out.println("Thread state: New");
        t1.start();
        System.out.println("Thread state: Runnable");
        t1.join();
        System.out.println("Thread state: Terminated");
    }
}
🔧 Methods

Important Thread Methods

start()
run()
sleep()
join()
yield()
interrupt()
isAlive()
setPriority()
currentThread()
getName()

Thread Methods Example

class ThreadMethodsDemo extends Thread {
    public void run() {
        System.out.println("Thread " + getName() + " running with priority " + getPriority());
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted");
        }
    }
    
    public static void main(String[] args) {
        ThreadMethodsDemo t1 = new ThreadMethodsDemo();
        ThreadMethodsDemo t2 = new ThreadMethodsDemo();
        
        t1.setName("Thread-1");
        t2.setName("Thread-2");
        t1.setPriority(Thread.MIN_PRIORITY); // 1
        t2.setPriority(Thread.MAX_PRIORITY); // 10
        
        t1.start();
        t2.start();
        
        System.out.println("Main thread: " + Thread.currentThread().getName());
        System.out.println("t1 is alive? " + t1.isAlive());
    }
}
🔧 Runnable Interface

Creating Threads Using Runnable Interface

Instead of extending the Thread class, we can implement the Runnable interface for more flexibility. This approach is preferred because Java supports single inheritance only, but multiple interfaces can be implemented.

Using Runnable Interface

// Define task using Runnable
class Task implements Runnable {
    private String taskName;
    
    Task(String name) {
        this.taskName = name;
    }
    
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(taskName + " - step " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                System.out.println(taskName + " interrupted");
            }
        }
        System.out.println(taskName + " completed");
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        // Create tasks
        Task task1 = new Task("Download File");
        Task task2 = new Task("Process Data");
        Task task3 = new Task("Send Email");
        
        // Create threads with tasks
        Thread t1 = new Thread(task1);
        Thread t2 = new Thread(task2);
        Thread t3 = new Thread(task3);
        
        // Start threads
        t1.start();
        t2.start();
        t3.start();
        
        System.out.println("All tasks submitted to threads");
    }
}

Output: All three tasks run concurrently, showing interleaved output

🔒 Synchronization

Thread Synchronization

When multiple threads access shared resources, synchronization is needed to prevent data inconsistency and race conditions. Java provides synchronized keyword to control access to shared resources.

Synchronization Example

class Counter {
    private int count = 0;
    
    // Synchronized method to prevent race condition
    public synchronized void increment() {
        count++;
    }
    
    public int getCount() {
        return count;
    }
}

class CounterThread extends Thread {
    private Counter counter;
    
    CounterThread(Counter counter) {
        this.counter = counter;
    }
    
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter.increment();
        }
    }
}

public class SynchronizationExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        
        // Create 5 threads
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new CounterThread(counter);
            threads[i].start();
        }
        
        // Wait for all threads to complete
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("Final count: " + counter.getCount());
        // Output: 5000 (with synchronization)
    }
}

Note: Without synchronization, the final count may be less than 5000 due to race conditions.

🎯 Priority

Thread Priority in Java

Each thread in Java has a priority. Thread priority ranges from 1 (MIN_PRIORITY) to 10 (MAX_PRIORITY), with default priority 5 (NORM_PRIORITY). Higher priority threads get more CPU time.

class PriorityDemo extends Thread {
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(getName() + " (Priority: " + getPriority() + ") - Count: " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }
    
    public static void main(String[] args) {
        PriorityDemo t1 = new PriorityDemo();
        PriorityDemo t2 = new PriorityDemo();
        PriorityDemo t3 = new PriorityDemo();
        
        t1.setName("Low Priority Thread");
        t2.setName("Normal Priority Thread");
        t3.setName("High Priority Thread");
        
        t1.setPriority(Thread.MIN_PRIORITY);   // 1
        t2.setPriority(Thread.NORM_PRIORITY);  // 5
        t3.setPriority(Thread.MAX_PRIORITY);   // 10
        
        t1.start();
        t2.start();
        t3.start();
    }
}

Note: Priority is a hint to the scheduler; actual execution order depends on the underlying OS.

🏆 Best Practices

Best Practices for Java Threads

Use Runnable interface instead of extending Thread class for better flexibility

Use synchronized keyword or Lock interface to prevent race conditions

Prefer Executor framework (ThreadPoolExecutor) for managing multiple threads

Avoid unnecessary thread creation; use thread pools for large number of short-lived tasks

Always handle InterruptedException properly when using sleep() or wait()

Use volatile keyword for variables accessed by multiple threads to ensure visibility

Use join() to wait for thread completion when needed

Prefer ConcurrentHashMap, BlockingQueue over manual synchronization for collections