Module: Multithreading and Concurrency

Thread Class

Java Core: Multithreading and Concurrency - Thread Class

This document outlines the Thread class in Java, a fundamental component for implementing multithreading and concurrency.

What is a Thread?

A thread is a lightweight sub-process, a unit of execution within a process. In Java, multithreading allows multiple parts of a program to execute concurrently, potentially improving performance and responsiveness.

The Thread Class

The java.lang.Thread class is the core class for working with threads in Java. It provides the necessary mechanisms to create, start, and manage threads.

Key Features:

  • Represents a thread of execution: Each Thread object represents a single thread.
  • Runnable Interface: Threads are typically created by providing a Runnable object that contains the code to be executed in the thread.
  • Lifecycle Management: Provides methods to control the thread's lifecycle (new, runnable, running, blocked, terminated).
  • Synchronization: Supports synchronization mechanisms to manage access to shared resources and prevent race conditions.

Creating Threads

There are two primary ways to create threads in Java:

1. Extending the Thread Class:

  • Create a class that extends Thread.
  • Override the run() method. This method contains the code that the thread will execute.
  • Create an instance of your thread class.
  • Call the start() method on the instance to begin execution.
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName() + " is running");
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread " + Thread.currentThread().getName() + ": Count = " + i);
            try {
                Thread.sleep(100); // Simulate some work
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted!");
            }
        }
    }
}

public class ThreadExample1 {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        thread1.setName("First Thread"); // Optional: Set a name for the thread
        thread1.start();

        MyThread thread2 = new MyThread();
        thread2.setName("Second Thread");
        thread2.start();
    }
}

Pros: Simple for basic threading tasks. Cons: Java only supports single inheritance. Extending Thread prevents your class from inheriting from another class. Less flexible.

2. Implementing the Runnable Interface:

  • Create a class that implements the Runnable interface.
  • Implement the run() method. This method contains the code that the thread will execute.
  • Create an instance of your Runnable class.
  • Create a Thread object, passing your Runnable instance to the constructor.
  • Call the start() method on the Thread object.
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName() + " is running");
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread " + Thread.currentThread().getName() + ": Count = " + i);
            try {
                Thread.sleep(100); // Simulate some work
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted!");
            }
        }
    }
}

public class ThreadExample2 {
    public static void main(String[] args) {
        MyRunnable runnable1 = new MyRunnable();
        Thread thread1 = new Thread(runnable1, "First Thread"); // Pass Runnable and optional name
        thread1.start();

        MyRunnable runnable2 = new MyRunnable();
        Thread thread2 = new Thread(runnable2, "Second Thread");
        thread2.start();
    }
}

Pros: More flexible. Allows your class to inherit from another class. Recommended approach. Cons: Slightly more verbose than extending Thread.

Important Thread Methods

  • start(): Starts the thread by calling the run() method. This is the only way to start a thread. Calling run() directly will not start a new thread.
  • run(): Contains the code that the thread will execute. This method is called by the start() method.
  • sleep(long millis): Causes the current thread to pause execution for the specified number of milliseconds. Can throw InterruptedException.
  • interrupt(): Interrupts the thread. If the thread is blocked (e.g., in sleep()), it will throw an InterruptedException.
  • isAlive(): Returns true if the thread is still running; otherwise, returns false.
  • join(): Waits for the thread to terminate. Can throw InterruptedException.
  • getName(): Returns the name of the thread.
  • setName(String name): Sets the name of the thread.
  • currentThread(): Returns a reference to the currently executing thread.
  • setPriority(int priority): Sets the thread's priority. Priorities are hints to the scheduler and are not guaranteed. Valid priorities are Thread.MIN_PRIORITY, Thread.NORM_PRIORITY, and Thread.MAX_PRIORITY.
  • getPriority(): Returns the thread's priority.
  • yield(): Causes the current thread to relinquish its remaining time slice to other threads. A hint to the scheduler.

Thread States

A thread can be in one of several states:

  • New: The thread has been created but has not yet started.
  • Runnable: The thread is capable of running but may not be running at any given moment.
  • Running: The thread is currently executing.
  • Blocked: The thread is waiting for a resource or event to occur (e.g., waiting for I/O, waiting for a lock).
  • Waiting: The thread is waiting indefinitely for another thread to signal it.
  • Timed Waiting: The thread is waiting for a specified amount of time for another thread to signal it.
  • Terminated: The thread has finished executing.

Thread Synchronization (Brief Mention)

When multiple threads access shared resources, synchronization is crucial to prevent race conditions and ensure data consistency. Java provides several synchronization mechanisms, including:

  • synchronized keyword: Used to create synchronized blocks of code or methods.
  • Lock interface: Provides more flexible synchronization options than synchronized.
  • Semaphore: Controls access to a limited number of resources.
  • CountDownLatch: Allows one or more threads to wait until a count reaches zero.

Best Practices

  • Use Runnable interface: Prefer implementing Runnable over extending Thread for greater flexibility.
  • Handle InterruptedException: Always handle InterruptedException when using methods like sleep() and join().
  • Avoid excessive synchronization: Synchronization can introduce performance overhead. Use it only when necessary.
  • Use thread pools: Thread pools can improve performance by reusing threads instead of creating new ones for each task.
  • Consider concurrency utilities: Java provides a rich set of concurrency utilities in the java.util.concurrent package that can simplify concurrent programming.

This provides a foundational understanding of the Thread class in Java. Further exploration of concurrency concepts like synchronization, thread pools, and the java.util.concurrent package is essential for building robust and efficient multithreaded applications.