1. Description of ThreadPoolExecutor parameters
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- corePoolSize: The size of the core thread pool.When a task is committed to the thread pool, the core thread pool creates a core thread to execute the task, even if other core threads are able to execute a new task, and waits until the number of tasks required to execute is greater than the base size of the core thread pool.If the prestartAllCoreThreads() method of the thread pool is called, the core thread pool will create and start all core threads ahead of time.
- workQueue: Task queue.When there are no threads in the core thread pool, the submitted tasks are temporarily queued.Java offers a variety of Blocking Queue.
- maximumPoolSize: The maximum number of threads allowed to be created in the thread pool.If the queue is full and the number of threads created is less than the maximum number of threads, the thread pool creates new idle threads to perform tasks.It is worth noting that this parameter does not work if an unbounded task queue is used.
- KeepAliveTime: When the number of threads in the thread pool is greater than corePoolSize, keepAliveTime is the maximum time that extra idle threads wait for a new task, after which the extra threads will be terminated.Therefore, if there are many tasks and each task takes less time to execute, you can increase the time and increase thread utilization.It is worth noting that this parameter does not work if an unbounded task queue is used.
- TimeUnit: The unit of time that thread activity is maintained.
-
threadFactory: The factory where threads are created.Each created thread can be given a business-compliant name through a thread factory.
// Dependent guava new ThreadFactoryBuilder().setNameFormat("xx-task-%d").build();
-
handler: Saturation policy.When the queue and thread pool are full, indicating that the thread pool is saturated, a strategy must be adopted to handle the new tasks submitted.Java offers the following four strategies:
- AbortPolicy: Default.Throw an exception directly.
- CallerRunsPolicy: Run the task only with the thread on which the caller is running.
- DiscardOldestPolicy: Discard the last task in the queue and execute the current task.
- DiscardPolicy: Do not process, discard.
tips: Typically we call threads in the core thread pool core threads, which are not recycled; beyond the task queue, the created threads are idle threads, which are recycled (recycle time is keepAliveTime)
2. Introduction to the common ThreadPoolExecutor
Executors are factory classes that create ThreadPoolExecutor and SchduledThreadPoolExecutor.
Java provides many types of ThreadPoolExecutor, more commonly FixedThreadPool, SingleThreadExecutor, CachedThreadPool, and so on.
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
FixedThreadPool is referred to as a thread pool that can reuse a fixed number of threads.You can see that both corePoolSize and maximumPoolSize are set to nThreads; keepAliveTime is set to 0L, which means that extra idle threads are immediately terminated; and the blocking queue LinkedBlockingQueue is used as the thread's working queue (queue capacity is Integer.MAX_VALUE).
The problem with FixedThreadPool is that since the capacity of the queue is Integer.MAX_VALUE, it can basically be considered unbound, neither the maximumPoolSize nor keepAliveTime parameters will work, nor will the saturated rejection policy be executed, causing a large number of tasks to accumulate in the blocked queue.
FixedThreadPool is suitable for scenarios where you need to limit the number of threads to meet resource management needs.
SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
SingleThreadExecutor is a thread pool that uses a single thread.You can see that corePoolSize and maximumPoolSize are set to 1, and the other parameters are the same as FixedThreadPool, so the risks are the same as FixedThreadPool, so let's not go into it.
SingleThreadExecutor works well for tasks that need to be sequentially executed.
CachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
CachedThreadPool is a thread pool that creates new threads as needed.You can see that corePoolSize is set to 0, so all threads created are idle; maximumPoolSize is set to Integer.MAX_VALUE (basically considered unbounded), which means that an unlimited number of idle threads can be created; keepAliveTime is set to 60L, which means that the maximum time an idle thread can wait for a new task is 60Seconds; use the unscaled SynchronousQueue as the working queue for the thread pool.
The problem with CachedThreadPool is that if the main thread is submitting tasks faster than the thread in maximumPool is processing them, CachedThreadPool will continue to create new threads.In extreme cases, CachedThreadPool will run out of CPU and memory resources by creating too many threads.
CachedThreadPool is suitable for applets that perform many short-term asynchronous tasks, or for servers that are lightly loaded.
3. Build your own ThreadPoolExecutor thread pool
Given the risks mentioned above, we prefer to use ThreadPoolExecutor to create a thread pool instead of an Executors factory.
The following is a Demo instance where ThreadPoolExecutor creates a thread pool:
public class Pool { static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("pool-task-%d").build(); static ExecutorService executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) throws ExecutionException, InterruptedException { // 1. Task Execution without Return Value - > Runnable executor.execute(() -> System.out.println("Hello World")); // 2. Task Execution with Return Value - > Callable Future<String> future = executor.submit(() -> "Hello World"); // The get method blocks thread execution waiting to return results String result = future.get(); System.out.println(result); // 3. Monitor Thread Pool monitor(); // 4. Close Thread Pool shutdownAndAwaitTermination(); monitor(); } private static void monitor() { ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Pool.executor; System.out.println("[Thread Pool Task) Maximum number of threads ever created in a thread pool:" + threadPoolExecutor.getLargestPoolSize()); System.out.println("[Thread Pool Task) Number of threads in the thread pool:" + threadPoolExecutor.getPoolSize()); System.out.println("[Thread Pool Task) Number of threads active in the thread pool:" + threadPoolExecutor.getActiveCount()); System.out.println("[Thread Pool Tasks) Number of tasks waiting to be executed in the queue:" + threadPoolExecutor.getQueue().size()); System.out.println("[Thread Pool Tasks) Number of tasks completed by the thread pool:" + threadPoolExecutor.getCompletedTaskCount()); } /** * Close Thread Pool * 1. shutdown,shutdownNow The principle is to traverse the worker threads in the thread pool and interrupt the threads one by one. * 2. shutdownNow: Set the state of the thread pool to STOP and then try to stop all threads executing or pausing tasks and return to the list of tasks waiting to be executed. * 3. shutdown: Set the state of the thread pool to SHUTDOWN and interrupt all threads that are not executing tasks. */ private static void shutdownAndAwaitTermination() { // Prohibit submission of new tasks executor.shutdown(); try { // Waiting for existing tasks to terminate if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { // Cancel the task currently in progress executor.shutdownNow(); // Wait for a while for the task response to be cancelled if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { System.err.println("Pool did not terminate"); } } } catch (InterruptedException ie) { // Cancel if the current thread is also interrupted executor.shutdownNow(); // Retain interrupt status Thread.currentThread().interrupt(); } } }
Creating a thread pool requires the following considerations:
- CPU-intensive tasks should be configured with as few threads as possible, such as configuring Ncpu+1 threads.
- IO-intensive tasks (database read and write, etc.) should be configured with as many threads as possible, such as Ncpu*2 threads.
- Tasks with different priorities can be handled using the priority queue PriorityBlockingQueue.
- Bounded queues are recommended.You can avoid creating a very large number of threads or even crashing your system.Bounded queues can increase the system's stability and early warning capabilities and can be set up a bit larger as needed, such as thousands.