Java Thread Class

Introduction

The Thread class in Java is a part of the java.lang package and provides the facility to create and manage threads. A thread is a lightweight subprocess, the smallest unit of processing. Multithreading in Java is a process of executing multiple threads simultaneously to maximize CPU utilization and improve the performance of applications.

Key Points:

  • Part of java.lang Package: The Thread class is included in the java.lang package, which is automatically imported.
  • Concurrent Execution: Allows concurrent execution of code.
  • Lightweight Process: A thread is lighter than a process and shares the same memory space.
  • Thread Lifecycle Management: Provides methods to manage the lifecycle of a thread.
  • Synchronization Support: Offers built-in support for synchronization.

Table of Contents

  1. Creating a Thread
  2. Thread Lifecycle
  3. Thread States
  4. Thread Methods with Examples
  5. Thread Synchronization
  6. Example: Comprehensive Usage of Thread Class
  7. Best Practices
  8. Real-World Analogy
  9. Conclusion

1. Creating a Thread

Extending the Thread Class

You can create a thread by extending the Thread class and overriding its run() method.

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

public class ThreadExample {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // Start the thread
    }
}

Implementing the Runnable Interface

You can also create a thread by implementing the Runnable interface and passing an instance of your class to a Thread object.

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

public class RunnableExample {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        t1.start();  // Start the thread
    }
}

2. Thread Lifecycle

A thread in Java goes through several stages in its lifecycle:

  1. New: A thread that has been created but not yet started.
  2. Runnable: A thread that is ready to run and waiting for CPU time.
  3. Blocked: A thread that is waiting for a monitor lock to enter or re-enter a synchronized block/method.
  4. Waiting: A thread that is waiting indefinitely for another thread to perform a particular action.
  5. Timed Waiting: A thread that is waiting for another thread to perform a particular action for a specified waiting time.
  6. Terminated: A thread that has exited.

3. Thread States

Example:

public class ThreadStatesExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);  // Timed waiting
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread is running...");
        });

        System.out.println("State: " + t1.getState());  // NEW
        t1.start();
        System.out.println("State: " + t1.getState());  // RUNNABLE
        try {
            Thread.sleep(500);
            System.out.println("State: " + t1.getState());  // TIMED_WAITING
            t1.join();
            System.out.println("State: " + t1.getState());  // TERMINATED
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4. Thread() Methods with Examples

4.1. Creating a Thread

Creating a thread by extending the Thread class and implementing the Runnable interface.

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

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // Start the thread
    }
}

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

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        t1.start();  // Start the thread
    }
}

4.2. Starting a Thread

The start() method is used to begin the execution of a thread. It invokes the run() method.

Example:

public class StartThreadExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Thread started");
        });
        thread.start();  // Start the thread
    }
}

4.3. Sleep Method

The sleep(long millis) method pauses the execution of the current thread for the specified number of milliseconds.

Example:

public class SleepExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread: " + i);
                try {
                    Thread.sleep(1000);  // Sleep for 1 second
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
}

4.4. Join Method

The join() method allows one thread to wait for the completion of another.

Example:

public class JoinExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread 1: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                t1.join();  // Wait for t1 to finish
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread 2: " + i);
            }
        });

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

4.5. Interrupt Method

The interrupt() method interrupts a thread, causing it to exit from a blocking state like sleep.

Example:

public class InterruptExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println("Thread was interrupted");
            }
        });

        thread.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

4.6. Checking if a Thread is Alive

The isAlive() method checks if a thread is still running.

Example:

public class IsAliveExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread.start();

        while (thread.isAlive()) {
            System.out.println("Thread is alive");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Thread has finished execution");
    }
}

4.7. Thread Priorities

Each thread has a priority, which helps the thread scheduler determine the order of thread execution. Use the setPriority(int priority) and getPriority() methods to set and get the thread’s priority.

Example:

public class ThreadPriorityExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> System.out.println("Thread t1 is running..."));
        Thread t2 = new Thread(() -> System.out.println("Thread t2 is running..."));

        t1.setPriority(Thread.MIN_PRIORITY);  // Set priority to 1
        t2.setPriority(Thread.MAX_PRIORITY);  // Set priority to 10

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

4.8. Setting and Getting Thread Name

You can set and get the name of a thread using the setName(String name) and getName() methods.

Example:

public class ThreadNameExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("Thread is running..."));
        thread.setName("MyThread");
        thread.start();

        System.out.println("Thread name: " + thread.getName());
    }
}

5. Thread Synchronization

Synchronization is used to control the access of multiple threads to shared resources. This ensures that only one thread can access the resource at a time, preventing data inconsistency.

Synchronized Method

class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

public class SynchronizationExample {
    public static void main(String[] args) {
        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();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Count: " + counter.getCount());  // Expected output: 2000
    }
}

Synchronized Block

class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

6. Example: Comprehensive Usage of Thread Class

Example: Multi-Threaded Sum Calculation

class SumTask extends Thread {
    private int[] arr;
    private int start;
    private int end;
    private int result;

    public SumTask(int[] arr, int start, int end) {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }

    public int getResult() {
        return result;
    }

    @Override
    public void run() {
        result = 0;
        for (int i = start; i < end; i++) {
            result += arr[i];
        }
    }
}

public class MultiThreadingExample {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int mid = arr.length / 2;

        SumTask task1 = new SumTask(arr, 0, mid);
        SumTask task2 = new SumTask(arr, mid, arr.length);

        task1.start();
        task2.start();

        try {
            task1.join();
            task2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        int totalSum = task1.getResult() + task2.getResult();
        System.out.println("Total Sum: " + totalSum);
    }
}

Output:

Total Sum: 55

7. Best Practices

  1. Avoid Busy Waiting: Use proper synchronization techniques (wait(), notify(), notifyAll()) instead of busy-waiting loops.
  2. Use Thread Pools: For managing multiple short-lived tasks, use thread pools provided by the ExecutorService to efficiently manage resources.
  3. Handle InterruptedException: Properly handle InterruptedException to ensure threads can be interrupted gracefully.
  4. Minimize Synchronized Sections: Keep synchronized sections as short as possible to avoid performance bottlenecks.
  5. Avoid Deadlocks: Design your synchronization strategy to avoid deadlocks by using techniques like lock ordering.
  6. Use Atomic Variables: Use atomic variables (AtomicInteger, AtomicBoolean) for simple thread-safe operations.
  7. Prefer High-Level Concurrency Utilities: Use high-level concurrency utilities from the java.util.concurrent package for complex synchronization and concurrency tasks.

8. Real-World Analogy

Consider a bank where multiple customers (threads) perform transactions (tasks) simultaneously. The bank system must ensure that:

  • Adding Transactions: Customers can deposit and withdraw money concurrently.
  • Handling Synchronization: Ensure that two customers do not withdraw money from the same account at the same time to prevent overdrafts.
  • Managing Multiple Requests: The bank system uses thread pools to handle multiple customer requests efficiently.
  • Ensuring Data Consistency: The system ensures data consistency by synchronizing access to shared resources.

9. Conclusion

The Thread class in Java is a fundamental building block for creating and managing threads, enabling concurrent execution of code. By understanding how to create, manage, and synchronize threads, you can effectively utilize multithreading in your Java applications. Following best practices will help you write efficient, thread-safe, and maintainable code.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top