riven

Riven

Riven

Threads in java with example

Threads are a fundamental concept in programming that enable concurrent execution of code, allowing applications to perform multiple operations simultaneously. In Java, threads play a vital role in achieving parallelism, improving performance, and enhancing the responsiveness of applications. 

What is a Thread?

A thread is a lightweight process that can execute tasks independently of the main program flow. Each thread has its own call stack, registers, and local variables but shares memory with other threads in the same process. This shared memory model facilitates communication between threads but also introduces the complexity of synchronization to avoid concurrent access issues.

Benefits of Using Threads

  1. Improved Performance: Threads allow for the execution of multiple tasks simultaneously, making better use of CPU resources.
  2. Responsiveness: In GUI applications, using threads prevents the interface from freezing while performing long-running tasks.
  3. Resource Sharing: Threads within the same process share memory and resources, allowing for efficient data sharing and communication.

Thread Lifecycle

A thread in Java goes through several states during its lifecycle:

  1. New: The thread is created but not yet started.
  2. Runnable: The thread is ready to run and waiting for CPU time.
  3. Blocked: The thread is blocked and waiting for a monitor lock to enter a synchronized block/method.
  4. Waiting: The thread is waiting indefinitely for another thread to perform a specific action (e.g., notify).
  5. Timed Waiting: The thread is waiting for another thread to perform an action for a specified waiting time.
  6. Terminated: The thread has completed its execution.

Thread State Diagram

Creating Threads in Java

There are two primary ways to create a thread in Java:

  1. Extending the Thread class: By creating a subclass of the Thread class and overriding its run() method.
  2. Implementing the Runnable interface: By creating a class that implements the Runnable interface and defining the run() method.

Method 1: Extending the Thread Class

				
					class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is running: " + Thread.currentThread().getName());
    }
}

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

        thread1.start(); // Starts the thread
        thread2.start(); // Starts another thread
    }
}
				
			

Explanation

  1. Subclassing Thread: The MyThread class extends the Thread class and overrides the run() method to define the thread’s behavior.
  2. Starting Threads: The start() method is called to begin the thread’s execution. This invokes the run() method in a new call stack.

Method 2: Implementing the Runnable Interface

				
					class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread is running: " + Thread.currentThread().getName());
    }
}

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

        thread1.start(); // Starts the thread
        thread2.start(); // Starts another thread
    }
}
				
			

Explanation

  1. Implementing Runnable: The MyRunnable class implements the Runnable interface and defines the run() method.
  2. Creating Threads: Instances of Thread are created with MyRunnable as the target. The start() method is called to execute the thread.

Thread Methods

Java provides several methods in the Thread class for thread management. Some commonly used methods include:

  • start(): Starts the execution of the thread.
  • run(): Defines the code to be executed by the thread.
  • sleep(long millis): Pauses the current thread for a specified time.
  • join(): Waits for the thread to die before proceeding.
  • interrupt(): Interrupts a thread, setting its interrupt flag.
  • isAlive(): Checks if the thread is still running.

Example of Using Thread Methods

				
					class SleepyThread extends Thread {
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " is sleeping for 2 seconds.");
            Thread.sleep(2000); // Sleep for 2 seconds
            System.out.println(Thread.currentThread().getName() + " has woken up.");
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " was interrupted.");
        }
    }
}

public class ThreadMethodExample {
    public static void main(String[] args) {
        SleepyThread thread = new SleepyThread();
        thread.start();

        try {
            thread.join(); // Wait for the thread to finish
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread continues.");
    }
}
				
			

Explanation

  1. Sleeping Thread: The SleepyThread class sleeps for 2 seconds during its execution.
  2. Join Method: The join() method is called to ensure the main thread waits for the SleepyThread to finish before proceeding.

Thread Synchronization

When multiple threads access shared resources, synchronization is necessary to prevent data inconsistency. Java provides several mechanisms for synchronization, including:

  1. Synchronized Methods: Marking a method as synchronized ensures that only one thread can execute it at a time.
  2. Synchronized Blocks: Fine-grained control over synchronization can be achieved using synchronized blocks.

Synchronized Methods

				
					class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

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

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

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

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

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

Explanation

  1. Synchronized Increment: The increment() method is synchronized, ensuring that only one thread can modify the count variable at a time.
  2. Concurrency: Two threads increment the counter concurrently, but the synchronized method ensures that the count remains accurate.

Synchronized Blocks

Synchronized blocks allow for more granular control over synchronization:

				
					class Counter {
    private int count = 0;

    public void increment() {
        synchronized (this) { // Synchronized block
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

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

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

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

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

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

Explanation

  1. Synchronized Block: The increment() method contains a synchronized block, allowing finer control over which part of the method is synchronized.
  2. Resource Sharing: This method allows for better performance when only certain sections of code need to be synchronized.

Thread Communication

Java provides mechanisms for thread communication using the wait(), notify(), and notifyAll() methods. These methods are used to manage the execution of threads that share resources.

Example of Thread Communication

				
					class SharedResource {
    private int count = 0;

    public synchronized void increment() {
        count++;
        notify(); // Notify waiting threads
    }

    public synchronized int getCount() throws InterruptedException {
        while (count == 0) {
            wait(); // Wait for count to be incremented
        }
        return count;
    }
}

public class ThreadCommunicationExample {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        Thread producer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                resource.increment();
                System.out.println("Incremented count to: " + resource.getCount());
            }
        });

        Thread consumer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("Count: " + resource.getCount());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        consumer.start();
        producer.start();
    }
}
				
			

Explanation

  1. Shared Resource: The SharedResource class maintains a count that is incremented by the producer thread.
  2. Waiting and Notifying: The consumer thread waits until the count is incremented. When the producer increments the count, it calls notify() to wake the waiting consumer.

Executors Framework

Java provides the Executors framework, which simplifies the management of threads. It allows for thread pooling and concurrent task execution.

Using ExecutorService

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

class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("Task is running in: " + Thread.currentThread().getName());
    }
}

public class ExecutorServiceExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3); // Pool of 3 threads

        for (int i = 0; i < 5; i++) {
            executor.execute(new Task());
        }

        executor.shutdown(); // Shutdown the executor
    }
}
				
			

Explanation

  1. Executor Service: An ExecutorService is created with a fixed thread pool size of 3.
  2. Submitting Tasks: The execute() method submits tasks to the executor for concurrent execution.
  3. Shutdown: The shutdown() method is called to terminate the executor after all tasks are completed.

Related topic

Java Unary Operators
Java unary operators Unary operators in Java are some of the simplest yet most powerful tools available...
Serialization in java
Serialization in java with example Serialization is a crucial concept in Java that allows you to convert...
Java Inheritance with example
Java inheritances with example Inheritances is a fundamental concept in object-oriented programming (OOP)...
Bean life cycle in spring
Bean life cycle in spring with example Spring Framework is a powerful tool for building Java applications,...
Interface in java with example
Interfaces in java with example Java is an object-oriented programming language that emphasizes principles...
Thread life cycle in java
Thread life cycle in java with java Java’s multithreading capabilities allow developers to create...
Java operators with example
Java operators with example Operators are special symbols that perform operations on variables and values....
Java checked exception
java Checked Exceptions Java’s exception handling framework is a powerful feature that helps developers...
Java arraylist with example
Java arraylist with example In Java, the ArrayList class is part of the Java Collections Framework and...
Java try-catch block
Java try-catch blocks Java is designed with a robust exception handling mechanism that helps manage errors...