data-driven-docs

Selft training repo


Project maintained by ggranados Hosted on GitHub Pages — Theme by mattgraham

Threads

Table of Contents


What’s a Thread

Conceptually, a thread in Java represents a separate path of execution within a Java program. It’s the smallest unit of a program that can be scheduled and executed independently. Threads allow a Java program to perform multiple tasks concurrently, making better use of available system resources, particularly in multi-core processors.

Here are some key concepts that define what a thread is in Java:

Thread Lifecycle

The thread lifecycle in Java includes the following states:

Back to top

Creation of threads

You can create a new thread by extending the Thread class and overriding its run() method. This approach allows you to define the behavior of the thread by implementing the run() method.

class MyThread extends Thread {
  public void run() {
    // Define the task to be executed by the thread
  }
}

MyThread thread = new MyThread();
thread.start(); // Start the thread

Back to top

A more flexible approach is to implement the Runnable interface. This separates the task from the threading mechanism and allows you to reuse the same task with multiple threads.

class MyRunnable implements Runnable {
  public void run() {
    // Define the task to be executed by the thread
  }
}

MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // Start the thread

Back to top

With Java 8 and later, you can use lambda expressions to simplify the creation of threads when implementing Runnable. This makes the code more concise.

Runnable myRunnable = () -> {
    // Define the task to be executed by the thread
};

Thread thread = new Thread(myRunnable);
thread.start(); // Start the thread

Back to top

The Executor framework provides a higher-level way to manage threads and execute tasks concurrently. It abstracts away many of the low-level details of thread management.

  Executor executor = Executors.newFixedThreadPool(2); // Create a thread pool
  executor.execute(() -> {
  // Define the task to be executed by a thread in the pool
  });

Back to top

If you need to execute tasks that return results or throw exceptions, you can use the Callable interface in combination with Future. This allows you to retrieve results or handle exceptions after a thread has completed its execution.

Callable<Integer> myCallable = () -> {
  // Define the task to be executed by the thread
  return 42;
};

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Integer> future = executorService.submit(myCallable);

Back to top

Java provides various concurrency utilities in the java.util.concurrent package, such as ExecutorService, ThreadPoolExecutor, and ForkJoinPool, which offer advanced features for managing and controlling thread execution.

Table of Differences

Concept Description Usage
Thread A class in Java representing a separate thread of execution. Extend the Thread class and override the run() method to define the thread’s behavior.
Runnable An interface in Java representing a task that can be executed by a thread. Implement the Runnable interface and override the run() method to define the task. Pass the Runnable to a Thread for execution.
Callable An interface similar to Runnable but allows tasks to return a result or throw exceptions. Implement the Callable interface and override the call() method to define the task. Execute it using an ExecutorService, which returns a Future representing the task’s result.

Back to top

Common Methods on Threads

Method: void start() Use: Initiates the execution of the thread. It calls the run() method, and the thread enters the “Runnable” state.

Back to top

Method: void join() Use: Causes the current thread to wait until the thread on which join() is called has completed its execution. Useful for thread coordination.

Back to top

Method: boolean isAlive() Use: Checks whether a thread is alive (i.e., has started and not yet terminated).

Back to top

Method: void interrupt() Use: Interrupts the thread, causing it to throw an InterruptedException if it’s currently in a blocking or waiting state. Used for thread interruption and termination.

Back to top

Method: boolean isInterrupted() Use: Checks if the thread has been interrupted. Returns true if it has been interrupted, false otherwise.

Back to top

Method: static boolean interrupted() Use: Checks if the current thread has been interrupted and clears the interrupted status. Returns true if the thread was interrupted, false otherwise.

Back to top

Method: static void sleep(long millis) or static void sleep(long millis, int nanos) Use: Causes the currently executing thread to sleep (pause) for the specified amount of time, allowing other threads to run.

Back to top

Method: static void yield() Use: Suggests to the scheduler that the current thread is willing to yield its current execution time, giving other threads a chance to run. It’s a hint and not a guarantee.

Back to top

Methods: void setName(String name) and String getName() Use: Sets or retrieves the name of the thread, which can be helpful for identification and debugging.

Back to top

Methods: void setPriority(int priority) and int getPriority() Use: Sets or retrieves the priority of the thread, indicating its importance to the thread scheduler.

Back to top

Method: void wait() or void wait(long timeout) or void wait(long timeout, int nanos)

Use: The wait() method is used for inter-thread communication and synchronization. When called within a synchronized context (i.e., within a synchronized block or method), it causes the current thread to release the lock it holds and enter a waiting state. The thread remains in this waiting state until another thread calls notify() or notifyAll() on the same object or until the specified timeout (if provided) elapses.

The wait() method is commonly used for implementing producer-consumer patterns, where one thread produces data and another thread consumes it. It’s also used for various synchronization scenarios where threads need to coordinate their activities based on certain conditions.

Back to top

Concurrency Issues

Race Conditions

A race condition occurs when multiple threads access shared data concurrently, and the final outcome depends on the timing and order of execution of the threads.

Race conditions can lead to unpredictable and incorrect behavior in a program because one thread may modify the shared data while another thread is reading or modifying it simultaneously.

To prevent race conditions, synchronization mechanisms like locks, synchronized blocks/methods, or the use of thread-safe data structures should be employed to ensure that only one thread can access the shared resource at a time.

Back to top

Deadlocks

A deadlock is a situation in which two or more threads are unable to proceed with their execution because each is waiting for a resource that another thread holds, resulting in a circular dependency.

Common conditions for a deadlock to occur are:

Deadlocks can lead to a complete halt in program execution, and they require careful design and use of strategies like deadlock detection and prevention to avoid or resolve them.

Back to top

Livelocks

A livelock is a situation where two or more threads are actively trying to resolve a resource conflict but end up repeatedly changing their states without making any progress.

In a livelock, threads are not blocked, but they are constantly responding to each other’s actions and remain in a non-productive loop.

Livelocks often occur when multiple threads are trying to be overly polite by repeatedly releasing and reacquiring resources, preventing any of them from making progress.

Livelocks can be mitigated by introducing randomness or timeouts into the thread behavior to break the repeated patterns and allow the threads to make progress.

Back to top


Ref.


Get Started | Languages | Java Development | Java 8 | Concurrency