Skip to main content

Threads in Java

  • A thread is a lightweight, independent path of execution within a program.

  • Java supports multithreading, allowing multiple threads to run concurrently within a single program, making it possible to perform many tasks at once.

  • In Java, threads are instances of the Thread class or objects that implement the Runnable interface.

Importance:

  • Improved Performance: Especially on multi-core processors, tasks can be performed simultaneously.

  • Responsive Applications: Allows background tasks like file downloading or data processing while keeping the main application responsive.

  • Resource Sharing: Threads within the same process share resources, which can lead to more efficient memory and resource usage.

Creating Threads:

There are several ways to create threads in Java:

  1. Extending the Thread class

  2. Implementing the Runnable interface

  3. Using Lambda Expressions for Anonymous Threads

Extending the Thread Class:

To create a thread by extending the Thread class, you create a new class that extends Thread and override its run() method.

Example:

class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Task is running: " + i);
}
}
}

public class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();

thread1.start();
thread2.start();
}
}

Output

Thread-1 - Task is running: 0
Thread-1 - Task is running: 1
Thread-1 - Task is running: 2
Thread-1 - Task is running: 3
Thread-1 - Task is running: 4
Thread-0 - Task is running: 0
Thread-0 - Task is running: 1
Thread-0 - Task is running: 2
Thread-0 - Task is running: 3
Thread-0 - Task is running: 4

Implementing the Runnable Interface:

To create a thread by implementing the Runnable interface, you create a new class that implements Runnable and pass an instance of this class to a Thread object.

Example:

class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Task is running: " + i);
}
}
}

public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());

thread1.start();
thread2.start();
}
}

Output

Thread-0 - Task is running: 0
Thread-0 - Task is running: 1
Thread-0 - Task is running: 2
Thread-0 - Task is running: 3
Thread-0 - Task is running: 4
Thread-1 - Task is running: 0
Thread-1 - Task is running: 1
Thread-1 - Task is running: 2
Thread-1 - Task is running: 3
Thread-1 - Task is running: 4

Anonymous Threads (Lambda Expression):

Java 8 introduced lambda expressions, which provide a concise way to create anonymous threads.

Example:

public class Main {
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Task is running: " + i);
}
};

Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);

thread1.start();
thread2.start();
}
}

Output

Thread-1 - Task is running: 0
Thread-1 - Task is running: 1
Thread-1 - Task is running: 2
Thread-1 - Task is running: 3
Thread-1 - Task is running: 4
Thread-0 - Task is running: 0
Thread-0 - Task is running: 1
Thread-0 - Task is running: 2
Thread-0 - Task is running: 3
Thread-0 - Task is running: 4

Thread States and Lifecycle:

Thread States:

Threads in Java can be in different states:

  • New: A thread that's been created but not yet started.

  • Runnable: A thread that's ready to run is moved to the runnable state.

  • Blocked: A thread that is blocked, waiting for a monitor lock.

  • Waiting: A thread that is waiting indefinitely for another thread to perform a particular action is in this state.

  • Timed Waiting: A thread that is waiting for another thread to perform a particular action within a stipulated amount of time.

  • Terminated: A thread that has exited is in this state.

Thread Lifecycle:

The lifecycle of a thread involves the following states:

  • New: When a thread is created.

  • Runnable: When the start method is called.

  • Blocked, Waiting, Timed Waiting: In various scenarios depending on the thread's actions.

  • Terminated: When the run method completes or an unhandled exception occurs.

Thread Methods:

  • start(): Begins the execution of a thread.

  • run(): Contains the code that constitutes the new thread.

  • sleep(milliseconds): Causes the current thread to pause for a specified period.

  • join(): Waits for the thread to die.

  • yield(): Causes the currently executing thread to pause and allow other threads to execute.

Thread Synchronization:Synchronized method:

When multiple threads access shared resources, data inconsistency and corruption can occur. Synchronization helps to ensure that only one thread can access a resource at a time.

Example:

class Counter {
private int count = 0;

public synchronized void increment() {
count++;
}

public synchronized int getCount() {
return count;
}
}

public class SynchronizationExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();

Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});

t1.start();
t2.start();

t1.join();
t2.join();

System.out.println("Count: " + counter.getCount());
}
}

Output:

Count: 2000

In this example, the increment() method is synchronized to ensure that only one thread can execute it at a time, preventing data corruption.

Thread Interruption:

Thread interruption is a way to signal a thread that it should stop its current work and do something else. This is useful for tasks that may need to be canceled or stopped before completion.

Example:

public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
System.out.println("Task is running: " + i);
Thread.sleep(500); // Simulate some work
}
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
}
});

thread.start();

try {
Thread.sleep(2000); // Main thread sleeps for 2 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}

thread.interrupt(); // Interrupt the worker thread
}
}

Output

Task is running: 0
Task is running: 1
Task is running: 2
Task is running: 3
Thread was interrupted

Thread Safety:

  • Immutable Objects: Immutable objects are inherently thread-safe as their state cannot be changed after creation.

  • Local Variables: Variables local to a method are thread-safe as each thread gets its own copy.

  • Synchronized Methods/Blocks: Use synchronized methods or blocks to control access to shared resources.

Thread Pools:

Thread pools manage a pool of worker threads and can be used to execute tasks without the overhead of creating new threads for each task. This is managed by the ExecutorService.

Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);

Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Task is running: " + i);
}
};

executorService.submit(task);
executorService.submit(task);
executorService.submit(task);

executorService.shutdown();
}
}

Output

pool-1-thread-3 - Task is running: 0
pool-1-thread-3 - Task is running: 1
pool-1-thread-3 - Task is running: 2
pool-1-thread-3 - Task is running: 3
pool-1-thread-1 - Task is running: 0
pool-1-thread-1 - Task is running: 1
pool-1-thread-1 - Task is running: 2
pool-1-thread-1 - Task is running: 3
pool-1-thread-1 - Task is running: 4
pool-1-thread-2 - Task is running: 0
pool-1-thread-2 - Task is running: 1
pool-1-thread-2 - Task is running: 2
pool-1-thread-2 - Task is running: 3
pool-1-thread-2 - Task is running: 4
pool-1-thread-3 - Task is running: 4

Summary:

  • Threads: Lightweight units of execution within a program.

  • Creation: Extend Thread, implement Runnable, or use lambda expressions for anonymous threads.

  • Thread States: New, Runnable, Blocked, Waiting, Timed Waiting, Terminated.

  • Thread Interruption: Provides a mechanism to signal a thread to stop its work.

  • Synchronization: Ensures safe access to shared resources.

  • Thread Safety: Achieved through immutable objects, local variables, and synchronized methods/blocks.

  • Thread Pools: Efficiently manage multiple threads with ExecutorService.

Understanding threads and concurrency in Java is crucial for writing efficient, responsive, and high-performance applications.