Introduction to Thread Pools
-
Java threads correspond to the threads of the system, and creating threads is expensive in system resources.
-
Too many threads consume more system resources, and context switching of threads incurs system resource overhead.
-
Thread pools can unify thread management, balance the direct relationship between threads and system resources, and improve system resource utilization and program stability.
-
Create a custom thread pool code as follows:
// Create a thread pool with specified parameters ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, // Maximum Number of Core Threads 2, // Maximum Threads = Number of Core Threads + Number of Temporary Threads 1000, // Maximum idle time for temporary threads beyond which they will be destroyed TimeUnit.MILLISECONDS, // Unit of time new ArrayBlockingQueue<Runnable>(1), // Container for task queues new MyThreadFactory(), // Thread Create Factory Class new MyRejectedExecutionHandler() // Task Rejection Processing Policy ); // Submit a task to the thread pool executor.submit(new MyRunner("hello"));
Thread pool related parameters
-
corePoolSize: Maximum number of core threads, resident threads.
-
maximumPoolSize: Maximum number of threads, temporary threads.
-
keepAliveTime: The maximum idle time of a temporary thread beyond which the temporary thread will be destroyed.
-
Unit: unit between when idle.
-
workQueue: A blocked queue for storing tasks. JDK provides:
- LinkedBlockingQueue: Chain list queue with maximum length of int type.
- SynchronousQueue: A forward queue of 0 length that forwards only to tasks and does not store tasks.
- DelayedWorkQueue: Delayed queue, data structure is heap, sorted by task delay time.
-
threadFactory: Threads create factory classes that can be created by implementing the java.util.concurrent.ThreadFactory interface.
-
handler: Task rejection policy, a policy executed when a thread pool is unable to process a task. You can create your own policy by implementing the java.util.concurrent.RejectedExecutionHandler interface.
The strategies provided by JDK are as follows:
- AbortPolicy: An exception is thrown when a task is rejected.
- DiscardPolicy: There is no hint when rejecting a task and there is a risk of data loss.
- DiscardOldestPolicy: Discard the task with the longest survival time when rejecting it.
- CallerRunsPolicy: When a task is rejected, the task is given to the submitted thread for execution, which can slow down the submission speed of the task submission thread and reduce thread pool pressure.
Thread pool task execution
- The first task committed to the thread pool is executed by the core thread.
- When a threaded task reaches the maximum number of core threads, it is committed to the task queue store.
- When the task queue is full, temporary threads continue to be created to perform tasks until the maximum number of threads is reached.
- When the maximum number of threads is reached for task execution, the thread pool can no longer receive new tasks and will enter the denial policy execution.
- When the number of task executions decreases, temporary threads are destroyed based on their idle time, reducing resource consumption.
JDK Built-in Thread Pool
Thread pool implementation class | Blocking Task Queue Used | Thread pool characteristics | Use precautions |
---|---|---|---|
Executors.newFixedThreadPool(5); | LinkedBlockingQueue | Fixed number of threads, maximum task queue capacity int | Task queue may cause OOM |
Executors.newCachedThreadPool(); | SynchronousQueue | Maximum number of core threads in int, task queue for forwarding only | Too many threads may cause OOM |
Executors.newSingleThreadExecutor(); | LinkedBlockingQueue | Number of core threads is one. Thread exceptions create new threads to execute tasks, ensuring the order in which tasks are executed | Task queue may cause OOM |
Executors.newScheduledThreadPool(5); | DelayedWorkQueue | Specify frequency to execute tasks, multiple core threads to execute | Task queue may cause OOM |
Executors.newSingleThreadScheduledExecutor(); | DelayedWorkQueue | Specify frequency to execute tasks, single core thread to execute | Task queue may cause OOM |
Number of Threads Selection
-
JDK built-in threads have a variety of flaws, and we need to customize our own thread pool to suit specific circumstances.
-
CPU-intensive tasks recommend twice as many core threads as CPU cores, and too many threads can cause frequent thread context switching, which reduces execution efficiency.
-
IO-intensive tasks recommend that the number of thread cores be several times the number of cpu cores, since IO reads are relatively slow and more threads can increase cpu utilization.
-
Computing methods recommended by Brain Goetz, author of Java Concurrent Programming Practice:
Number of threads = CPU Number of cores *( 1 + Average wait time / Average working time)
-
Too many or too few threads can affect the speed of task execution, so we can categorize tasks and execute them according to different thread pools. The coordination of system resources should also be noted.
Custom Thread Pool
package cn.devzyh.learning.threadpool; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * Custom Thread Pool Parameters */ public class ThreadPoolParamTest { /** * Implement your own thread to create a factory class * It can also be created from third-party tool classes, such as: * Implemented through the Builder of com.google.common.util.concurrent.ThreadFactory */ static class MyThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; public MyThreadFactory() { this.namePrefix = "TestPool-" + poolNumber.getAndIncrement() + "-TestThread-"; } @Override public Thread newThread(Runnable r) { // Specify a custom thread name return new Thread(r, this.namePrefix + threadNumber.getAndIncrement()); } } /** * Implement your own Task Denial Handling Policy * Tasks that exceed thread pool processing power will execute this policy * Java There are four built-in rejection strategies: * AbortPolicy: Throw an exception for AbortExecutionException * DiscardPolicy: Discard newly submitted tasks without any notifications * DiscardOldestPolicy: Discard the task at the head of the task queue without any notification * CallRunsPolicy: Deleting a task to a thread submitting it allows it to execute and delays the submission of a new task */ static class MyRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("Thread pool full, retry task in 1 second"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } executor.submit(r); // retry System.out.println("Task to resubmit to thread pool"); } } /** * Thread Tasks */ static class MyRunner implements Runnable { private String taskName; public MyRunner(String name) { this.taskName = name; } @Override public void run() { System.out.println("Start Task Execution[" + this.taskName + "],Current thread name:" + Thread.currentThread().getName()); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task Execution Completed[" + this.taskName + "]"); } public String getTaskName() { return this.taskName; } } public static void main(String[] args) { // Create a thread pool of custom parameters ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, // Maximum Number of Core Threads 2, // Maximum Threads = Number of Core Threads + Number of Temporary Threads 1000, // Maximum idle time for temporary threads beyond which they will be destroyed TimeUnit.MILLISECONDS, // Unit of time new ArrayBlockingQueue<Runnable>(1), // Container for task queues new MyThreadFactory(), // Thread Create Factory Class new MyRejectedExecutionHandler() // Task Rejection Processing Policy ); executor.submit(new MyRunner("core")); // A core thread is created to perform the task executor.submit(new MyRunner("queue")); // At this point, the number of core threads reaches its maximum, and the current task is submitted to the task queue to wait executor.submit(new MyRunner("temp")); // When the task queue is full, a temporary thread will be created to execute the task executor.submit(new MyRunner("reject")); // The thread's ability to execute tasks at this time is exceeded to execute the task rejection policy executor.shutdown(); // Close Thread Pool } }