1. Extending the Thread class

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

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

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

Explanation:

  • start() initiates a new thread and calls the run() method internally.
  • Avoid calling run() directly; it will execute in the main thread instead of a new one.

2. Implementing the Runnable interface

This approach is more flexible because it allows your class to extend other classes, as Java supports single inheritance.

class MyRunnable implements Runnable {
    @Override public void run() {
        System.out.println("Thread is running: " + Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable()); // Passing Runnable to Thread 
        thread.start(); // Starting the thread 
    }
}

Explanation:

  • You implement the Runnable interface and pass it to the Thread constructor.
  • Runnable is preferred when your class needs to inherit from another class.

3. Using Lambda Expressions (Java 8 and above)

This method simplifies the creation of threads using functional programming.

public class LambdaThread {
    public static void main(String[] args) {
            Thread thread = new Thread(() - > {
                System.out.println("Thread is running: " + Thread.currentThread().getName());
            });
            thread.start(); // Starting the thread
            } 
    
}

Explanation:

  • The Runnable interface is a functional interface, so you can use a lambda expression to provide the implementation of the run() method.

4. Using ExecutorService (Thread Pool)

If you need to manage a pool of threads efficiently, use ExecutorService.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(() - > {
            System.out.println("Thread 1 is running: " + Thread.currentThread().getName());
        });
        executor.submit(() - > {
            System.out.println("Thread 2 is running: " + Thread.currentThread().getName());
        });
        executor.shutdown(); // Shutdown the executor 
    }
}

Explanation:

  • ExecutorService provides better management of threads and should be preferred in complex applications.

Summary

ApproachWhen to Use
Extending ThreadWhen you don’t need to extend another class.
Implementing RunnableWhen you want more flexibility and reusability.
Using Lambda ExpressionWhen working with Java 8+ for concise code.
Using ExecutorServiceWhen managing a pool of threads efficiently.

Each method has its advantages. Use ExecutorService for large-scale or production applications, while extending Thread or using Runnable works well for simpler cases.

The preferred way to create threads in Java is by using the Runnable interface or an ExecutorService (thread pool). Let’s discuss the reasons and the context in which each approach is recommended.


Why Runnable is Preferred Over Extending Thread

1. Separation of Task and Thread Logic

  • By implementing the Runnable interface, the task to be executed is kept separate from the actual thread logic. This provides better design flexibility.

2. Single Inheritance in Java

  • Since Java allows a class to extend only one superclass, if you extend Thread, you cannot extend any other class. Implementing Runnable allows the class to inherit from other useful classes if needed.

3. Reusability and Code Maintenance

  • Using Runnable promotes code reusability. You can pass the same Runnable instance to multiple threads if needed, which makes it easier to maintain.

For large-scale or production applications, ExecutorService is preferred.

Benefits of ExecutorService over Manual Thread Creation:

  1. Better Thread Management
    • Creating threads manually can lead to performance issues when the number of threads becomes large. A thread pool manages threads efficiently by reusing existing threads instead of creating new ones for each task.
  2. Avoiding Resource Exhaustion
    • With thread pools, you avoid uncontrolled thread creation that could exhaust CPU or memory. Thread pools restrict the maximum number of concurrent threads.
  3. Task Queueing
    • Tasks submitted to an ExecutorService are queued and executed when threads are available, preventing congestion and improving system stability.
  4. Graceful Shutdown and Exception Handling
    • ExecutorService provides methods to gracefully shutdown the thread pool and handle exceptions more effectively, which is crucial in production systems.

ApproachWhen to Use
Implementing RunnableWhen creating a few lightweight threads for short-lived tasks.
Extending ThreadWhen a specialized thread needs its own state (but this is rare and discouraged).
ExecutorServiceWhen dealing with many tasks that need to be executed concurrently, especially in production.

Conclusion: Preferred Approach

  • For simple concurrent tasks: Use Runnable with the Thread class.
  • For scalable, production-level applications: Use ExecutorService or thread pools.

The use of Runnable is encouraged over Thread for flexibility and good design practices. For complex scenarios involving multiple tasks and threads, ExecutorService or other concurrency frameworks (like ForkJoinPool) are preferred.

In Java, a daemon thread is a background thread that runs continuously to provide services or perform tasks for other threads. The JVM will exit when all non-daemon threads (also called user threads) have finished execution, even if daemon threads are still running. Essentially, the JVM doesn’t wait for daemon threads to finish before shutting down.

Key Characteristics of Daemon Threads:

  1. Background services: Typically used for background tasks like garbage collection, logging, or monitoring.
  2. No blocking on shutdown: JVM exits when only daemon threads remain, regardless of whether they are completed.
  3. Lower priority: Daemon threads have lower priority than user threads.
  4. Lifecycle depends on user threads: If no user threads are running, the daemon threads terminate automatically.

How to Create a Daemon Thread in Java

You can create a daemon thread by calling the setDaemon(true) method on a thread before it starts.

public class DaemonThreadExample {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() - > {
            while (true) {
                try {
                    System.out.println("Daemon thread is running...");
                    Thread.sleep(1000);
                    // Sleep for 1 second 
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
            }
        });
        // Setting the thread as a daemon
        daemonThread.setDaemon(true);
        daemonThread.start();
        System.out.println("Main thread is terminating...");
    }
}

Output:

Main thread is terminating...

Daemon thread is running...

Daemon thread is running...

Here, the main thread will terminate quickly, and since the daemon thread is running in the background, the JVM will exit immediately without waiting for the daemon thread to complete its infinite loop.


Checking if a Thread is a Daemon

You can use the isDaemon() method to check if a thread is a daemon.

System.out.println("Is daemon thread: " + daemonThread.isDaemon());


Real-world Examples of Daemon Threads

  1. Garbage Collector (GC) in the JVM runs as a daemon thread to reclaim memory.
  2. Timer tasks that perform periodic background tasks like cleanup or logging.
  3. Idle connection management in server applications.

Key Points to Remember

  • A user thread can outlive a daemon thread if it takes longer to complete.
  • Always set a thread to daemon mode before calling start(). Otherwise, it will throw IllegalThreadStateException.
  • Be cautious when using daemon threads for critical tasks because they can terminate abruptly when the JVM exits.

A real-time example of a daemon thread could be a background task like auto-saving documents, logging, or cleaning up idle connections. Below is an example that simulates auto-saving a file every few seconds using a daemon thread.


Real-Time Example: Auto-Save Feature Using a Daemon Thread

Imagine an application where a user works on a document, and the daemon thread saves the content every 5 seconds in the background.

Code:

public class AutoSaveExample {
// Simulate the auto-save task in a daemon thread
static class AutoSaveTask implements Runnable {
@Override public void run() {
while (true) {
try {
System.out.println(“Auto-saving document…”);
Thread.sleep(5000); // Sleep for 5 seconds
} catch (InterruptedException e) {
System.out.println(“Auto-save interrupted.”);

            }
        }
    }
}
public static void main(String[] args) {
    Thread autoSaveThread = new Thread(new AutoSaveTask()); // Set the thread as daemon before starting it
    autoSaveThread.setDaemon(true);
    autoSaveThread.start(); // Simulate user working on the document 
    System.out.println("User is working on the document...");
    try {
        Thread.sleep(10000);
        // Simulate 10 seconds of user work 

    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("User has finished working. Exiting the application...");
    // The application will exit, and the daemon thread will terminate. 

}

}


Output:

User is working on the document...

Auto-saving document...

Auto-saving document...

User has finished working.

Exiting the application...


Explanation:

  1. Main Thread (User Work):
    • The main thread simulates the user working on a document for 10 seconds.
  2. Daemon Thread (Auto-Save):
    • The daemon thread runs in the background, printing “Auto-saving document…” every 5 seconds.
    • When the main thread finishes (after 10 seconds), the JVM exits immediately, terminating the daemon thread.
  3. Behavior of Daemon Thread:
    • Even though the auto-save task is infinite, the JVM terminates it as soon as the main thread completes because it’s running as a daemon thread.

Why Use a Daemon Thread Here?

  • Non-critical task: Auto-saving is important but not critical. If the user closes the application, the auto-save task doesn’t need to continue.
  • Automatic termination: The JVM ensures that the daemon thread doesn’t block the shutdown, so the user can exit quickly.

This is a practical use case of a daemon thread, where the background task assists the primary thread but doesn’t prevent the application from exiting.

If you have only daemon threads running in your Java application and no active work in the main thread, the JVM will terminate immediately once the main thread finishes execution. Here’s why and how it behaves:

Key Concepts:

  1. Main Thread: When your program starts, the main method runs on the main thread.
  2. Daemon Threads: These are background threads that the JVM doesn’t wait for when all non-daemon (user) threads finish or exit. Daemon threads are used for tasks like garbage collection or other background services.
  • JVM exits immediately after the main thread completes, even if daemon threads are still running.
  • Since daemon threads don’t block the JVM from exiting, the application will shut down as soon as there is no non-daemon thread left, including the main thread.

Example to Illustrate:

public class DaemonExample {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            while (true) {
                System.out.println("Daemon thread running...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        daemonThread.setDaemon(true); // Set the thread as daemon
        daemonThread.start();

        System.out.println("Main thread finished.");
        // No further code in main thread, JVM will exit here
    }
}

Output:

Main Thread Finished

The JVM exits right after printing “Main thread finished” because:

  • The main thread finishes execution.
  • Since only a daemon thread is running and the JVM doesn’t wait for daemon threads, the process ends, and the daemon thread is stopped without completing its infinite loop.

How to Keep the JVM Alive:

If you need the JVM to continue running, you must ensure that:

  1. A non-daemon (user) thread remains active, or
  2. Add some activity in the main thread (e.g., Thread.sleep() or join()).

Here’s an example with a user thread:

public class UserThreadExample {
    public static void main(String[] args) {
        Thread userThread = new Thread(() -> {
            while (true) {
                System.out.println("User thread running...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        userThread.start(); // Not a daemon thread

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

Now the JVM will keep running because the user thread is not marked as a daemon.

Conclusion:

  • If the main thread finishes and only daemon threads remain, the JVM shuts down immediately.
  • To prevent termination, you need either user threads or some blocking activity in the main thread.

In Java, thread priority determines the relative importance of a thread for the CPU’s scheduling. The JVM thread scheduler uses these priorities to decide when to switch between threads. However, it’s important to note that thread priority is only a hint to the operating system, and its effect can vary depending on the OS and JVM implementation.


How Thread Priority Works

  • Each thread in Java is assigned a priority value between 1 (lowest) to 10 (highest).
  • The default priority of a thread is 5.
  • The Java scheduler will generally prefer higher-priority threads over lower-priority threads, but it doesn’t guarantee strict order—other factors like time-slicing and fairness also play a role.

Java Thread Priority Constants

Java provides three constants defined in the Thread class:

public static final int MIN_PRIORITY = 1;  // Minimum priority
public static final int NORM_PRIORITY = 5; // Default priority
public static final int MAX_PRIORITY = 10; // Maximum priority

Setting and Getting Thread Priority

You can use the following methods from the Thread class:

  • Set priority:
thread.setPriority(int priority);

Throws IllegalArgumentException if the priority is not in the range of 1 to 10.
Get priority:
int priority = thread.getPriority();

Example: Setting Thread Priorities

class MyThread extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName() + 
                           " with priority " + Thread.currentThread().getPriority());
    }
}

public class ThreadPriorityExample {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        t1.setPriority(Thread.MIN_PRIORITY);  // Priority 1
        t2.setPriority(Thread.NORM_PRIORITY); // Priority 5
        t3.setPriority(Thread.MAX_PRIORITY);  // Priority 10

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

Output:

The output might differ each time due to the non-deterministic behavior of thread scheduling. However, you may observe that the higher-priority thread (t3) usually completes earlier.


How Priority Affects Thread Execution

  • Higher-priority threads are given preference but are not guaranteed to run before others.
  • Time-slicing: The JVM can split the CPU time among threads with equal priority.
  • Some OSes ignore thread priority, so it may not have any significant effect on them.

Key Points to Remember

  1. Platform Dependency: Thread priority behavior depends on the OS and JVM. Some operating systems may ignore Java thread priorities.
  2. Fairness: Prioritizing threads does not mean starvation won’t occur. To avoid starvation, thread scheduling policies like fair scheduling are used.
  3. Use with Caution: Over-reliance on thread priorities can make your code difficult to maintain and platform-dependent.

Common Issues with Thread Priorities

  1. Starvation: If higher-priority threads keep executing, lower-priority threads may starve (never get CPU time).
  2. Platform Inconsistencies: On some platforms, all threads might be treated equally, regardless of priority.
  3. Multicore Systems: Thread priorities are less relevant on modern multicore processors, where threads run in parallel on different cores.

The deprecated stop(), suspend(), and resume() methods in Java’s Thread class were removed due to various issues, such as deadlocks, data inconsistency, and thread state corruption. Instead, safe and modern alternatives involve using flags, wait(), notify(), and Interrupts to control thread behavior gracefully.

Below are examples that demonstrate starting, pausing, resuming, and stopping threads safely.

class ControlledThread implements Runnable {
    private volatile boolean isPaused = false;
    private volatile boolean isStopped = false;

    @Override
    public void run() {
        System.out.println("Thread started.");

        while (!isStopped) {
            synchronized (this) {
                // If the thread is paused, wait until notified to resume
                while (isPaused) {
                    try {
                        System.out.println("Thread paused...");
                        wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt(); // Handle interrupt properly
                        System.out.println("Thread interrupted during pause.");
                    }
                }
            }

            // Perform some work
            System.out.println("Thread running...");
            try {
                Thread.sleep(1000); // Simulate some work
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Thread interrupted during sleep.");
            }
        }

        System.out.println("Thread stopped.");
    }

    // Method to pause the thread
    public void pause() {
        isPaused = true;
    }

    // Method to resume the thread
    public synchronized void resumeThread() {
        isPaused = false;
        notify(); // Notify the thread to resume work
    }

    // Method to stop the thread
    public void stop() {
        isStopped = true;
    }
}

Driver Code to Demonstrate Usage

public class ThreadControlDemo {
    public static void main(String[] args) throws InterruptedException {
        ControlledThread controlledTask = new ControlledThread();
        Thread thread = new Thread(controlledTask);

        thread.start(); // Start the thread

        Thread.sleep(3000); // Let the thread run for 3 seconds

        controlledTask.pause(); // Pause the thread
        Thread.sleep(2000); // Simulate pause duration

        controlledTask.resumeThread(); // Resume the thread
        Thread.sleep(3000); // Let the thread run again

        controlledTask.stop(); // Stop the thread gracefully
        thread.join(); // Wait for the thread to finish

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

Explanation

  1. Flags for Control:
    • We use a volatile boolean flag (isPaused) to control when the thread should pause.
    • Another volatile boolean flag (isStopped) determines if the thread should stop.
  2. Pausing the Thread:
    • When pause() is called, the isPaused flag is set to true.
    • Inside the run() method, the thread checks this flag and calls wait() if it’s paused.
  3. Resuming the Thread:
    • When resumeThread() is called, isPaused is set to false and notify() is invoked to wake up the thread.
  4. Stopping the Thread:
    • When stop() is called, the isStopped flag is set to true, which causes the loop to terminate gracefully.
  5. Handling Interruptions:
    • The thread uses Thread.currentThread().interrupt() to handle interruptions properly, ensuring it can exit safely without locking resources.

Sample Output

mathematica

Copy code

Thread started. Thread running... Thread running... Thread running... Thread paused... Thread running... Thread running... Thread running... Thread stopped. Main thread exiting.


Advantages of this Approach

  • Graceful control: No risk of deadlocks or data inconsistencies.
  • Safe pause/resume using wait()/notify().
  • Interrupt handling ensures the thread exits properly.

This approach ensures better thread management without the pitfalls of deprecated methods.

wait() and sleep()

wait() and sleep() in Java both pause the execution of a thread, but they have different purposes, behavior, and use cases. Here’s a detailed comparison:


Comparison: wait() vs sleep()

Featurewait()sleep()
ClassDefined in java.lang.ObjectDefined in java.lang.Thread
Lock/Monitor DependencyRequires a synchronized block/methodNo lock or monitor required
Releases Monitor/LockYes, releases the monitorNo, retains the lock if held
ResumptionMust be resumed using notify() or notifyAll()Resumes automatically after timeout
Use CaseInter-thread communication (e.g., producer-consumer)Delays thread execution temporarily
Interrupt HandlingThrows InterruptedExceptionThrows InterruptedException
Static MethodNo (called on objects)Yes (Thread.sleep())
Blocking StateBlocks the current thread until notified or timed outBlocks for a fixed amount of time
Impact on CPU SchedulingPauses thread but allows others to executePauses thread but others continue execution

Detailed Behavior

1. wait() – Inter-Thread Communication Tool

  • Purpose: Used when one thread needs to wait for a condition to be met, typically in producer-consumer scenarios.
  • How it works:
    • A thread that calls wait() enters the waiting state and releases the monitor (lock).
    • It will only resume when another thread calls notify() or notifyAll() on the same monitor object.
public class WaitExample {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread waiting...");
                    lock.wait(); // Wait until notified
                    System.out.println("Thread resumed.");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.out.println("Thread interrupted.");
                }
            }
        });

        waitingThread.start();

        Thread.sleep(2000); // Main thread sleeps to simulate some work
        synchronized (lock) {
            System.out.println("Main thread notifying...");
            lock.notify(); // Notify the waiting thread
        }
    }
}

Output :

Thread waiting...
Main thread notifying...
Thread resumed.

2. sleep() – Pausing Execution Temporarily

  • Purpose: Used when a thread needs to be paused for a specific amount of time (e.g., rate limiting or periodic tasks).
  • How it works:
    • The thread that calls Thread.sleep() pauses for the specified time.
    • It does not release any lock during the sleep period.
public class SleepExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Thread started.");
            try {
                Thread.sleep(2000); // Pause for 2 seconds
                System.out.println("Thread resumed after sleep.");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("Thread interrupted.");
            }
        });

        thread.start();
    }
}

Output :

Thread started.
Thread resumed after sleep.

Key Differences Explained with Use Cases

Use Case for wait()

  • Producer-Consumer Problem:
    When a consumer thread needs to wait for a product to be available, it calls wait() on a shared object. Once the producer creates a product, it calls notify() to wake up the consumer.

Use Case for sleep()

  • Scheduled Tasks:
    If you want to perform an action at fixed intervals (e.g., sending a heartbeat every 5 seconds), you can use Thread.sleep().

Summary of Key Differences

  • wait() is used for inter-thread communication and relies on synchronization, while sleep() is used to pause execution temporarily without affecting locks.
  • wait() releases the monitor lock, while sleep() does not.
  • notify() or notifyAll() is needed to wake up a waiting thread, but a sleeping thread resumes automatically.

Choosing between wait() and sleep() depends on your use case:

  • Use wait() when threads need to communicate.
  • Use sleep() when you want to pause a thread for a fixed amount of time.

stop() and yield()

In Java, both stop() and yield() relate to thread behavior, but they serve different purposes. Since stop() is deprecated, understanding its risks and knowing safe alternatives is crucial. Here’s a detailed comparison of stop() vs yield(), along with their use cases.


Comparison: stop() vs yield()

Featurestop()yield()
Classjava.lang.Threadjava.lang.Thread
PurposeForcefully stops a threadHints the scheduler to pause and allow other threads to execute
Deprecation StatusDeprecated (since Java 2)Not deprecated (but rarely used)
Effect on Thread StateTerminates thread immediatelyThread moves to ready state
Releases LocksYes, abruptly releases locksNo lock involvement
Potential for Data IssuesHigh (can cause deadlocks, data corruption)No known risks
Control by DeveloperNot recommended (uncontrollable termination)Scheduler decides (no guarantee other threads will run)
AlternativeUse a flag or interrupt()Use thread priorities or better scheduling mechanisms
Impact on CPU SchedulingStops the thread; CPU moves to other tasksHelps in cooperative multitasking
Interrupt HandlingN/A (forcefully stops)Not directly related to interrupts

Leave a Reply

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