Types of Threads and How Thread works Internally in Java ?

In Java, threads represent independent paths of execution in a program. Threads are essential for multitasking, parallel processing, and handling concurrent tasks. Java provides different types of threads and mechanisms to create and manage them.

Types of Threads in Java

1. User Threads vs. Daemon Threads

Java threads can be classified into two main categories based on their lifecycle and how they affect the running of the application:

1.1 User Threads

  • Description: User threads are high-priority threads that the JVM waits for to complete before shutting down the application. The program continues to run until all user threads have finished their execution.
  • Use Cases: They are generally used for main tasks in an application, such as handling business logic or processing client requests.

Example:

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

    public static void main(String[] args) {
        Thread t = new Thread(new UserThreadExample());
        t.start(); // This thread is a user thread by default
    }
}

1.2 Daemon Threads

  • Description: Daemon threads are low-priority threads that run in the background and provide services to user threads. The JVM does not wait for daemon threads to complete before exiting the program; they are terminated once all user threads finish.
  • Use Cases: Daemon threads are generally used for background tasks such as garbage collection, monitoring tasks, or logging.

How to Set a Thread as Daemon:

public class DaemonThreadExample implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("Daemon thread is running");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread daemonThread = new Thread(new DaemonThreadExample());
        daemonThread.setDaemon(true);  // Set the thread as daemon
        daemonThread.start();

        // Main thread will finish, and daemon thread will stop running
        System.out.println("Main thread finished");
    }
}

2. Single-thread vs. Multi-thread

2.1 Single-thread

  • Description: A single-threaded application executes one thread at a time. All tasks are executed sequentially, and no multitasking is involved.
  • Use Cases: Used when tasks can be completed sequentially and do not require parallel execution.

Example:

public class SingleThreadExample {
    public static void main(String[] args) {
        System.out.println("Start task 1");
        // Simulate task 1
        System.out.println("Start task 2");
        // Simulate task 2
    }
}

2.2 Multi-thread

  • Description: A multi-threaded application runs multiple threads concurrently, enabling multiple tasks to be performed in parallel.
  • Use Cases: Used in situations where parallel execution can improve performance, such as handling multiple client requests, background tasks, or computational parallelism.

Example:

public class MultiThreadExample implements Runnable {
    private String threadName;

    public MultiThreadExample(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public void run() {
        System.out.println(threadName + " is running");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new MultiThreadExample("Thread 1"));
        Thread t2 = new Thread(new MultiThreadExample("Thread 2"));

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

3. Threads Based on Creation Methods

3.1 Thread Class

  • Description: In this method, you extend the Thread class and override its run() method to define the task that the thread will execute.
  • Use Cases: This method is used when you want to create a simple thread and don’t need to extend another class.

Example:

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

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

3.2 Runnable Interface

  • Description: In this method, you implement the Runnable interface and pass the object to the Thread class. This approach is preferred when you need to extend another class because Java does not support multiple inheritance.
  • Use Cases: Used when you want more flexibility in your class hierarchy.

Example:

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

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

3.3 Callable and Future

  • Description: Callable is similar to Runnable, but it can return a result and throw checked exceptions. Future is used to get the result of a Callable after execution.
  • Use Cases: Used when the thread needs to return a result or throw exceptions.

Example:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableExample implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 10;
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(new CallableExample());

        System.out.println("Result from thread: " + future.get());

        executor.shutdown();
    }
}

4. Types Based on Thread Pools

4.1 Fixed Thread Pool

  • Description: Creates a thread pool with a fixed number of threads. If all threads are busy, new tasks are queued until a thread becomes available.
  • Use Cases: Suitable when you know the number of tasks to execute concurrently.

Example:

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

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

        for (int i = 0; i < 5; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Thread is executing a task");
                }
            });
        }

        executor.shutdown();
    }
}

4.2 Cached Thread Pool

  • Description: A thread pool that creates new threads as needed, but reuses previously created threads when available.
  • Use Cases: Useful when you have many short-lived tasks.

Example:

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

public class CachedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();

        for (int i = 0; i < 5; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Thread is executing a task");
                }
            });
        }

        executor.shutdown();
    }
}

4.3 Scheduled Thread Pool

  • Description: A thread pool that can schedule commands to run after a delay or to execute periodically.
  • Use Cases: Used for scheduling tasks such as recurring maintenance or reporting.

Example:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

        scheduler.scheduleAtFixedRate(() -> System.out.println("Scheduled task"), 0, 1, TimeUnit.SECONDS);
    }
}

Conclusion

  • User threads are the main workers in a Java application, while daemon threads handle background tasks.
  • You can create threads using the Thread class, Runnable interface, or Callable interface.
  • Thread pooling is crucial for efficient resource management, with fixed thread pools, cached thread pools, and scheduled thread pools being the common types.
    Understanding the different types of threads in Java helps developers manage concurrency, optimize performance, and avoid thread-related issues such as deadlocks or excessive resource consumption.

Related Posts

Leave a Reply

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