9 Ways to Create Thread Pools with Java Executors

  sonic0002        2024-07-19 23:07:09       1,172        0    

In Java, the Executors class provides a variety of static factory methods to create different types of thread pools. When learning about thread pools, the Executors class is indispensable. Mastering its usage, principles, and scenarios will enable you to apply it smoothly in actual project development. Here are some commonly used methods, which I will explain one by one:

  • newCachedThreadPool(): Creates a cached thread pool. If a thread has not been used for 60 seconds, it will be terminated and removed from the cache.
  • newFixedThreadPool(int nThreads): Creates a fixed-size thread pool where nThreads specifies the number of threads in the pool.
  • newSingleThreadExecutor(): Creates a single-threaded executor that creates a single worker thread to execute tasks.
  • newScheduledThreadPool(int corePoolSize): Creates a fixed-size thread pool that can create new threads as needed, but executes tasks with a fixed delay according to a given initial delay.
  • newWorkStealingPool(int parallelism): Creates a work-stealing thread pool that uses multiple queues, with each thread stealing tasks from its own queue.
  • newSingleThreadScheduledExecutor(): Creates a single-threaded scheduler executor that can create new threads to execute tasks as needed.
  • privilegedThreadFactory(): Creates a thread factory to create threads with privileged access.
  • defaultThreadFactory(): Creates a default thread factory to create threads with non-privileged access.
  • unconfigurableExecutorService(ExecutorService executor): Converts the given ExecutorService into an unconfigurable version so that the caller cannot modify its configuration.

These methods provide flexible ways to create and manage thread pools to meet different concurrency needs. Below, I will introduce the implementation and usage scenarios of these 9 methods in detail.

newCachedThreadPool()

The newCachedThreadPool method is a static factory method in the Executors class within the Java java.util.concurrent package. This method creates a cached thread pool that can dynamically create new threads as needed and terminate idle threads that have not been used for a certain period.

Implementation Principle

  • Thread Creation: When a task is submitted to the thread pool, new threads are created if the current number of threads is less than the core pool size.
  • Thread Reuse: If the current number of threads equals the core pool size, new tasks are placed in a task queue awaiting execution.
  • Thread Termination: Idle threads that remain unused for a specified time (default is 60 seconds) are terminated to reduce resource consumption.

Source Code Analysis

In Java's java.util.concurrent package, the Executors class doesn't directly provide the implementation of newCachedThreadPool. Instead, it uses the ThreadPoolExecutor constructor. Below is an example of how ThreadPoolExecutor is called:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                   60L, TimeUnit.SECONDS,
                                   new SynchronousQueue());
}

Parameter Explanation

  • corePoolSize: The number of core threads, set to 0 here, indicating that the thread pool does not retain any core threads.
  • maximumPoolSize: The maximum number of threads, set to Integer.MAX_VALUE, theoretically allowing an unlimited number of threads.
  • keepAliveTime: The maximum time that excess idle threads will wait for new tasks before terminating, set to 60 seconds here.
  • unit: The time unit for the keepAliveTime parameter, which is seconds.
  • workQueue: A task queue. Here, it uses SynchronousQueue, a blocking queue that does not store elements, requiring each insert operation to wait for a corresponding remove operation.

Implementation Process

1. Initialization: When newCachedThreadPool is called, it creates a ThreadPoolExecutor instance.

2. Task Submission: When tasks are submitted to the thread pool, the pool checks if there are idle threads available to execute the tasks.

3. Thread Creation: If there are no idle threads and the current number of threads is less than maximumPoolSize, new threads are created to execute the tasks.

4. Task Queue: If the current number of threads has reached maximumPoolSize, tasks are placed in the SynchronousQueue awaiting execution.

5. Thread Reuse: After a thread completes its task, it doesn't terminate immediately but attempts to fetch new tasks from the SynchronousQueue.

6. Thread Termination: If a thread does not receive new tasks within the keepAliveTime, it terminates.

This design makes newCachedThreadPool ideal for handling a large number of short-lived tasks because it can dynamically adjust the number of threads to accommodate the varying workload. However, since it can create an unlimited number of threads, it's essential to consider the characteristics of the tasks and the system's resource limits to prevent resource exhaustion.

Use Case

Suitable for executing many short-term asynchronous tasks, especially when the execution time of the tasks is uncertain. Examples include handling a large number of concurrent requests on a web server or asynchronous logging.

By understanding the detailed implementation and principles of newCachedThreadPool, developers can better leverage this method in real-world projects to achieve efficient concurrency management.

newFixedThreadPool(int nThreads)

The newFixedThreadPool(int nThreads) method is a static factory method in the Executors class within the Java java.util.concurrent package. This method creates a thread pool with a fixed number of threads, ensuring that the number of threads in the pool remains constant.

Implementation Principle

  • Fixed Thread Count: The number of threads in the thread pool remains constant at nThreads.
  • Task Queue: Tasks submitted to the pool are executed by core threads first. If all core threads are busy, new tasks are placed in a blocking queue waiting for execution.
  • Thread Reuse: Threads in the pool are reused; after completing a task, a thread will immediately try to fetch the next task from the queue for execution.

Source Code Analysis

The newFixedThreadPool method is implemented by calling the constructor of the ThreadPoolExecutor class. Here is an example of how ThreadPoolExecutor is invoked:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
        nThreads, 
        nThreads, 
        0L,      
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue() 
    );
}

Parameter Explanation

  • corePoolSize: The number of core threads, set to nThreads, indicating that the pool always has nThreads threads.
  • maximumPoolSize: The maximum number of threads, also set to nThreads, ensuring the pool size does not exceed nThreads.
  • keepAliveTime: The maximum time that excess idle threads will wait for new tasks before terminating, set to 0 here, meaning any threads above the core pool size will terminate immediately.
  • unit: The time unit for the keepAliveTime parameter, which is milliseconds.
  • workQueue: A task queue. Here, it uses LinkedBlockingQueue, a blocking queue based on a linked list that can store an arbitrary number of tasks.

Implementation Process

1. Initialization: When newFixedThreadPool is called, a ThreadPoolExecutor instance is created.

2. Task Submission: When tasks are submitted to the pool, it checks if there are idle core threads available to execute the tasks immediately.

3. Task Queue: If all core threads are busy, new tasks are placed in the LinkedBlockingQueue waiting for execution.

4. Thread Reuse: Core threads, after completing a task, will try to fetch new tasks from the LinkedBlockingQueue for continued execution.

5. Thread Count Control: Since keepAliveTime is set to 0, any threads above the core pool size will terminate immediately, ensuring the number of threads in the pool does not exceed nThreads.

This design makes newFixedThreadPool ideal for handling a large and steady stream of tasks because it ensures tasks are executed in parallel with a fixed number of threads, avoiding the uncontrolled growth of threads. However, since the pool size is fixed, if the rate of task submission exceeds the pool's processing capacity, tasks may end up waiting in the queue for a long time. Therefore, when using newFixedThreadPool, it's important to set nThreads based on the characteristics of the tasks and the expected workload.

Use Case

Suitable for executing a large number of long-running tasks where the number of threads needs to be fixed. For example, running multiple data loading or data processing tasks simultaneously while limiting concurrency to avoid resource overload.

newSingleThreadExecutor()

The newSingleThreadExecutor method is a static factory method in the Executors class within the Java java.util.concurrent package. This method creates a single-threaded executor that ensures all tasks are executed sequentially in the order they are submitted, using a single thread.

Implementation Principle

  • Single-Thread Execution: The thread pool contains only one thread, ensuring all tasks are executed sequentially by this single thread.
  • Task Queue: If a new task is submitted while the single thread is busy, the task is placed in a blocking queue waiting for execution.
  • Thread Reuse: The single thread is reused; after completing a task, it will immediately try to fetch the next task from the queue for execution.

Source Code Analysis

The newSingleThreadExecutor method is implemented by calling the constructor of the ThreadPoolExecutor class. Here is an example of how ThreadPoolExecutor is invoked:

public static ExecutorService newSingleThreadExecutor() {
    return new ThreadPoolExecutor(
        1, 
        1, 
        0L, TimeUnit.MILLISECONDS, 
        new LinkedBlockingQueue() 
    );
}

Parameter Explanation

  • corePoolSize: The number of core threads, set to 1, indicating the pool always has one core thread.
  • maximumPoolSize: The maximum number of threads, also set to 1, ensuring the pool size does not exceed one thread.
  • keepAliveTime: The maximum time that excess idle threads will wait for new tasks before terminating, set to 0 here, meaning the thread will terminate immediately if idle.
  • unit: The time unit for the keepAliveTime parameter, which is milliseconds.
  • workQueue: A task queue. Here, it uses LinkedBlockingQueue, a blocking queue that can store an arbitrary number of tasks.

Implementation Process

1. Initialization: When newSingleThreadExecutor is called, a ThreadPoolExecutor instance is created.

2. Task Submission: When tasks are submitted to the pool, if the core thread is idle, it will execute the task immediately; if the core thread is busy, the task will be placed in the LinkedBlockingQueue waiting.

3. Sequential Execution: Since there is only one thread, all tasks are executed in the order they are submitted.

4. Task Queue: If the core thread is executing a task, new tasks are placed in the LinkedBlockingQueue waiting.

5. Thread Reuse: The core thread, after completing a task, will try to fetch new tasks from the LinkedBlockingQueue for continued execution.

6. Thread Count Control: Since keepAliveTime is set to 0, the core thread will terminate immediately if there are no tasks to execute. However, because both corePoolSize and maximumPoolSize are 1, the thread pool will immediately recreate a new thread.

This design makes newSingleThreadExecutor ideal for scenarios that require guaranteed sequential execution of tasks, such as when tasks have dependencies or must be executed in a specific order. Additionally, since there is only one thread, it avoids concurrency issues inherent in multi-threaded environments. However, the single-thread execution also limits parallel processing capabilities; if a task takes a long time to execute, subsequent tasks may experience significant delays. Therefore, when using newSingleThreadExecutor, it is important to consider the nature of the tasks and the requirement for sequential execution.

Use Cases

  • Guaranteed Sequential Execution: Suitable for scenarios that require tasks to be executed in a specific order, such as sequential processing of messages or events in a queue.
  • Single Background Thread for Periodic Tasks: Suitable for situations that require a single background thread to continuously handle periodic tasks.

By using newSingleThreadExecutor, developers can ensure that tasks are executed in the order they are submitted, without the complexity and potential issues of managing multiple threads.

newScheduledThreadPool(int corePoolSize)

The newScheduledThreadPool method is a static factory method in the Java java.util.concurrent package's Executors class. This method is used to create a thread pool with a fixed size that supports the execution of scheduled and periodic tasks.

Implementation Principle

  • Scheduled Tasks: The thread pool can execute tasks with a specified delay or at fixed intervals.
  • Fixed Thread Count: The number of threads in the pool is limited to the size specified by corePoolSize.
  • Task Queue: Tasks are executed by the core threads first. If all core threads are busy, new tasks are placed in a delay queue waiting for execution.

Source Code Analysis

The newScheduledThreadPool method is implemented by calling the constructor of the ScheduledThreadPoolExecutor class. Here is an example of how it is invoked:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

The ScheduledThreadPoolExecutor is a subclass of ThreadPoolExecutor, specifically designed for executing scheduled tasks. The corePoolSize parameter of the ScheduledThreadPoolExecutor constructor defines the number of core threads in the pool.

Internally, the ScheduledThreadPoolExecutor uses a DelayedWorkQueue as its task queue, which sorts tasks according to their scheduled execution time.

Implementation Process

1. Initialization: When newScheduledThreadPool is called, a ScheduledThreadPoolExecutor instance is created.

2. Task Submission: When a task is submitted to the pool, it is placed into the DelayedWorkQueue based on its scheduled execution time.

3. Task Scheduling: Threads in the pool retrieve tasks from the DelayedWorkQueue and execute them when their scheduled time arrives.

4. Thread Reuse: Threads that complete a task will attempt to fetch the next task from the DelayedWorkQueue.

5. Thread Count Control: If the number of tasks exceeds what the core threads can handle, the ScheduledThreadPoolExecutor will create new threads to help process the tasks, up to the limit defined by corePoolSize.

Characteristics

  • Thread Factory: ScheduledThreadPoolExecutor allows setting a thread factory to create threads with specific properties.
  • Rejected Execution Handler: It allows setting a RejectedExecutionHandler that handles tasks that cannot be accepted (e.g., when the pool is shut down or the task queue is full).
  • Shutdown Behavior: Unlike ThreadPoolExecutor, the shutdown and shutdownNow methods of ScheduledThreadPoolExecutor do not wait for delayed tasks to complete.

The newScheduledThreadPool method is ideal for scenarios that require the execution of scheduled tasks, such as periodic background tasks or tasks scheduled to run at specific times. However, since it is based on a fixed-size thread pool, under high load, tasks may be queued and wait for execution. Therefore, it's important to consider the appropriate corePoolSize to meet performance requirements during design.

Use Cases

  • Periodic Task Execution: Suitable for scenarios requiring periodic task execution, such as regular data backups or periodic status checks.
  • Delayed Task Execution: Suitable for tasks that need to be executed at a future point in time, such as sending reminders or scheduled updates.

By using newScheduledThreadPool, developers can efficiently manage and schedule tasks that need to be executed at fixed intervals or specific times, ensuring that the tasks are handled properly by a fixed number of threads.

newWorkStealingPool(int parallelism)

The newWorkStealingPool method is a static factory method in the java.util.concurrent package's Executors class, introduced in Java 8. This method creates a work-stealing thread pool that enhances the execution efficiency of parallel tasks, especially on multi-processor systems.

Implementation Principle

  • Work Stealing: In a work-stealing thread pool, each thread has its own task queue. When a thread finishes its tasks, it attempts to "steal" tasks from other threads' queues.
  • Parallelism Level: The size of the thread pool is determined by the parallelism parameter, which typically equals the number of processor cores on the host.
  • Dynamic Adjustment: The work-stealing thread pool can dynamically add or remove threads to adapt to the load of tasks and the utilization of threads.

Source Code Analysis

The newWorkStealingPool method is implemented by calling the static factory method of the ForkJoinPool class. Here is an example of how it is invoked:

public static ExecutorService newWorkStealingPool(int parallelism) {
    return new ForkJoinPool(
        parallelism,
        ForkJoinPool.defaultForkJoinWorkerThreadFactory,
        null, 
        false 
    );
}

Parameter Explanation

  • parallelism: The parallelism level, which is the number of threads in the thread pool.
  • ForkJoinPool.defaultForkJoinWorkerThreadFactory: The default thread factory used to create threads.
  • null: Uncaught exception handler, which is not specified here, so if a task throws an uncaught exception, it will be propagated to the caller of the ForkJoinTask.
  • false: Indicates that this is not an asynchronous task.

The ForkJoinPool internally uses ForkJoinWorkerThread to execute tasks, and each thread has a ForkJoinQueue to store tasks.

Implementation Process

1. Initialization: When newWorkStealingPool is called, a ForkJoinPool instance is created.

2. Task Submission: When tasks are submitted to the pool, they are placed in the local queue of the calling thread.

3. Task Execution: Each thread first attempts to execute tasks from its local queue.

4. Work Stealing: If the local queue is empty, the thread tries to steal tasks from other threads' queues.

5. Dynamic Adjustment: The thread pool can dynamically add or remove threads as needed.

Characteristics

  • Work Stealing: This mechanism is particularly suitable for unevenly distributed workloads, as it reduces idle time and improves resource utilization.
  • Parallel Computation: Suitable for parallel computing tasks that can be decomposed into multiple subtasks, as tasks can be divided and subtasks submitted to the thread pool.
  • Reduced Contention: Since each thread has its own queue, lock contention is reduced, enhancing concurrency performance.

The newWorkStealingPool is ideal for scenarios requiring high concurrency and high throughput, particularly on multi-processor systems. However, due to the work-stealing mechanism, it may not be suitable for scenarios with very short task execution times or very few tasks, as the stealing itself can introduce additional overhead.

Use Cases

  • Uneven Workloads: Ideal for tasks with uneven workloads or tasks that can be decomposed into multiple smaller tasks, such as image processing or data analysis.
  • Multi-core Processors: Effective utilization of all cores on multi-core processors.

By using newWorkStealingPool, developers can efficiently manage and execute parallel tasks, leveraging the full power of multi-core processors and improving the performance of concurrent applications.

newSingleThreadScheduledExecutor()

The newSingleThreadScheduledExecutor is a static factory method in the java.util.concurrent package's Executors class. This method creates a single-threaded scheduled executor that can schedule commands to run after a given delay or to execute periodically.

Implementation Principle

  • Single-threaded Execution: The executor ensures that all tasks are executed sequentially in a single thread, maintaining the order of task execution.
  • Scheduled Tasks: Supports both delayed and periodic task execution.
  • Task Queue: All tasks are first placed in a task queue, then executed sequentially by a single thread.

Source Code Analysis

The newSingleThreadScheduledExecutor method is implemented by calling the constructor of the ScheduledThreadPoolExecutor class. Here is an example of how it is invoked:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new ScheduledThreadPoolExecutor(1);
}

Here, ScheduledThreadPoolExecutor is an implementation of ExecutorService designed specifically for executing scheduled tasks. The constructor has one parameter, which is the core pool size. Setting it to 1 means this is a single-threaded executor.

The ScheduledThreadPoolExecutor internally uses a DelayedWorkQueue as the task queue. This queue can sort tasks according to their scheduled execution times.

Implementation Process

1. Initialization: When newSingleThreadScheduledExecutor is called, a ScheduledThreadPoolExecutor instance is created with a core pool size of 1.

2. Task Submission: When a task is submitted to the executor, it is wrapped into a ScheduledFutureTask or RunnableScheduledFuture and placed in the DelayedWorkQueue.

3. Task Scheduling: The single thread continuously fetches tasks from the DelayedWorkQueue and executes them at their scheduled times. If the execution time has arrived, the task is executed; if not, the thread waits until the execution time arrives.

4. Sequential Execution: Since there is only one thread, all tasks are executed in the order they are submitted.

5. Periodic Tasks: For tasks that need to be executed periodically, the executor recalculates the next execution time after each execution and places the task back into the queue.

Characteristics

  • Order Maintenance: newSingleThreadScheduledExecutor is ideal for scenarios where task order needs to be maintained, such as tasks with dependencies or specific sequences.
  • Concurrency Simplification: Having only one thread avoids concurrency issues, simplifying task synchronization and state management.
  • Scheduled Execution: It provides robust scheduling capabilities for tasks, such as periodic maintenance or background tasks.

Use Cases

  • Sequential Task Execution: Suitable for scenarios where tasks need to be executed in a specific order or have dependencies.
  • Scheduled Background Tasks: Ideal for tasks that need to be executed at regular intervals, such as periodic system checks or maintenance tasks.

privilegedThreadFactory()

The privilegedThreadFactory is a static factory method in the java.util.concurrent package's Executors class. This method creates a thread factory that produces threads with privileged access. These threads can access system properties, load system libraries, and access the file system.

Implementation Principle

  • Privileged Access: The threads created by this factory have permissions to access system resources, such as loading system properties and libraries.
  • Thread Creation: The thread factory creates new thread instances that inherit the context of the thread that created them.

Source Code Analysis

The implementation details of the privilegedThreadFactory method are not publicly exposed in the standard Java library. However, we can understand its functionality by examining how it generally works. Here is an example of how the privilegedThreadFactory method might be called:

public static ThreadFactory privilegedThreadFactory() {
    return new PrivilegedThreadFactory();
}

Here, PrivilegedThreadFactory is a private static inner class in the Executors class that implements the ThreadFactory interface. The ThreadFactory interface defines the newThread(Runnable r) method for creating new threads.

Implementation Process

1. Initialization: When the privilegedThreadFactory method is called, it returns a new instance of PrivilegedThreadFactory.

2. Thread Creation: When this factory is used to create a thread, it calls the newThread(Runnable r) method.

3. Privileged Access: In the newThread(Runnable r) method implementation, the AccessController.doPrivileged method is used to ensure that the newly created thread has privileged access.

4. Context Inheritance: The new thread typically inherits the context of the thread that created it, including the class loader and other settings.

Example

Although we cannot view the exact implementation of privilegedThreadFactory, we can provide a sample implementation to demonstrate how privileged threads can be created:

public class PrivilegedThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        return AccessController.doPrivileged(new PrivilegedAction<>() {
            @Override
            public Thread run() {
                return new Thread(r);
            }
        });
    }
}

In this example, PrivilegedAction is an anonymous class implementing the PrivilegedAction interface, where the run method creates a new thread. The AccessController.doPrivileged method executes a privileged action to ensure the thread creation process has the necessary permissions.

Characteristics

  • Privileged Access: Threads created using privilegedThreadFactory can access sensitive system resources, making it suitable for applications needing such access.
  • Simplified Security: Using AccessController.doPrivileged ensures that threads are granted the appropriate security permissions during their execution.
  • Context Inheritance: New threads inherit the context of their parent thread, which includes the class loader and other security settings.

Use Cases

  • Accessing System Resources: Suitable for applications that need threads to have higher privileges to access system properties or perform file I/O operations.
  • Security-Sensitive Operations: Ensures that threads executing security-sensitive operations have the necessary permissions to avoid security exceptions.

defaultThreadFactory()

The defaultThreadFactory is a static factory method in the java.util.concurrent package's Executors class. This method creates a default thread factory that produces standard threads with no special permissions.

Implementation Principle

  • Standard Thread Creation: The thread factory generates threads with default properties.
  • Thread Naming: The threads created have a default name prefix, usually in the form "pool-x-thread-y," where x and y are numbers.
  • Thread Priority: The thread priority is set to Thread.NORM_PRIORITY, which is the default priority for Java threads.
  • Non-Daemon Threads: The threads created are non-daemon threads, meaning their existence will prevent the JVM from exiting.

Source Code Analysis

The detailed implementation of the defaultThreadFactory method is not fully exposed, but we can infer its behavior from the ThreadFactory interface and some available source code snippets.

Here is a typical invocation of the defaultThreadFactory method:

public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}

DefaultThreadFactory is a private static inner class in the Executors class that implements the ThreadFactory interface. The ThreadFactory interface defines the newThread(Runnable r) method for creating new threads.

Implementation Process

1. Initialization: When the defaultThreadFactory method is called, it returns a new instance of DefaultThreadFactory.

2. Thread Creation: When this factory is used to create a thread, it calls the newThread(Runnable r) method.

3. Thread Naming: The newThread(Runnable r) method creates a new Thread object and sets a default thread name.

4. Thread Group Assignment: The new thread is assigned to a default thread group.

5. Thread Priority and Daemon Status: The thread priority is set to the default value, and the thread is set to non-daemon if it is initially a daemon thread.

Example

While the exact implementation of defaultThreadFactory is not visible, we can provide a sample implementation to demonstrate how default attribute threads can be created:

public class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                     poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

In this example, DefaultThreadFactory uses AtomicInteger to ensure the uniqueness of the pool and thread numbers. The created thread names have the prefix "pool-x-thread-y," where x and y are incrementing numbers. The threads are non-daemon, and their priority is set to Thread.NORM_PRIORITY.

Characteristics

  • Standard Thread Properties: Threads created using defaultThreadFactory have standard Java thread properties.
  • Non-Specialized Use: This thread factory is suitable for applications that do not require special permissions.
  • Non-Daemon Threads: The existence of these threads prevents the JVM from exiting until all non-daemon threads have completed execution.

Use Cases

  • Standard Applications: Suitable for most standard applications that need threads with default properties.
  • ExecutorService Implementation: Commonly used as the default choice for ExecutorService implementations when no special thread properties are required.

unconfigurableExecutorService(ExecutorService executor)

The unconfigurableExecutorService method in the java.util.concurrent package's Executors class creates an unconfigurable wrapper around an ExecutorService. This means that once the wrapped ExecutorService is created, its configuration cannot be changed, such as modifying the pool size or task queue.

Implementation Principle

  • Encapsulation: Wraps the existing ExecutorService in an unconfigurable proxy.
  • Immutable Configuration: Any method calls to change the configuration, such as shutdown, shutdownNow, setCorePoolSize, etc., will throw an UnsupportedOperationException.
  • Delegation: Other method calls are delegated to the original ExecutorService.

Source Code Analysis

The unconfigurableExecutorService method's detailed implementation is not fully exposed as it is part of the Executors class's private methods. However, we can infer its behavior based on the ExecutorService interface and proxy mechanism.

Here is a typical invocation of the unconfigurableExecutorService method:

public static ExecutorService unconfigurableExecutorService(ExecutorService executor) {
    return new FinalizableDelegatedExecutorService(executor);
}

FinalizableDelegatedExecutorService is a private static inner class in the Executors class that implements the ExecutorService interface and delegates method calls to another ExecutorService.

Implementation Process

1. Initialization: When the unconfigurableExecutorService method is called, it returns a new instance of FinalizableDelegatedExecutorService that takes the original ExecutorService as a parameter.

2. Method Interception: Calls to FinalizableDelegatedExecutorService methods are intercepted first.

3. Configuration Modification Interception: If the method is for modifying the configuration, such as shutdown or shutdownNow, it throws an UnsupportedOperationException.

4. Delegate Other Calls: Calls that do not involve configuration modification, such as submit or execute, are delegated to the original ExecutorService.

Example

Here is an example implementation that demonstrates how to create an unconfigurable ExecutorService proxy:

public class UnconfigurableExecutorService implements ExecutorService {
    private final ExecutorService executor;

    public UnconfigurableExecutorService(ExecutorService executor) {
        this.executor = executor;
    }

    @Override
    public void shutdown() {
        throw new UnsupportedOperationException("Shutdown not allowed");
    }

    @Override
    public List<Runnable> shutdownNow() {
        throw new UnsupportedOperationException("Shutdown not allowed");
    }

    @Override
    public boolean isShutdown() {
        return executor.isShutdown();
    }

    @Override
    public boolean isTerminated() {
        return executor.isTerminated();
    }

    @Override
    public void execute(Runnable command) {
        executor.execute(command);
    }
}

In this example, UnconfigurableExecutorService intercepts the shutdown and shutdownNow methods and throws an UnsupportedOperationException. Other methods are directly delegated to the original ExecutorService.

Characteristics

  • Immutable Configuration: The created ExecutorService wrapper ensures that the thread pool's configuration cannot be changed externally.
  • Safety in Shared Environments: Useful for preventing accidental changes to the thread pool's state, improving safety in multi-threaded environments.
  • Maintains Original Behavior: Other methods retain the behavior of the original ExecutorService.

Use Cases

  • Shared Thread Pools: Suitable when multiple components share the same thread pool, preventing one component from accidentally modifying the configuration.
  • Controlled Environments: Useful in environments where thread pool configuration should remain consistent and unaltered.

Reference: https://segmentfault.com/a/1190000045052043

JAVA THREAD  TUTORIAL 

       

  RELATED


  0 COMMENT


No comment for this article.