Pool technology
Pool technology is a very common programming skill. Database connection pool, thread pool and object pool are common in our daily work. Their characteristics are to maintain "expensive" and "time-consuming" resources in a specific "pool", and specify their minimum connection number, maximum connection number, blocking queue and other configurations, so as to facilitate unified management and reuse, It usually comes with some supporting functions such as detection mechanism, forced recovery and monitoring.
Thread Pool concept
As the name suggests, thread pool can be understood as a pool with threads. This pool can be used to better manage threads uniformly. Thread pool technology can help us manage threads and avoid increasing the resource consumption of creating and destroying threads. We can reuse existing threads through thread pool, so as to avoid creating them every time we use them.
Abstraction of thread pool in Java
The abstraction of thread pool in Java is the Executor interface, but the real thread pool implementation is ThreadPoolExecutor. This class provides four constructs for creating thread pool. The specific methods will be explained in detail below. In addition, Java also provides several commonly configured thread pools. The factory methods provided by Executors can be used to quickly create thread pools. In fact, these factory methods also directly or indirectly use the construction method of ThreadPoolExecutor to create thread pools. The specific methods will be explained in detail below.
ThreadPoolExecutor
ThreadPoolExecutor is the specific implementation of thread pool in Java, which is under the java.util.concurrent package. It provides four constructs for configuring and creating thread pools. The four construction methods are as follows:
/** * @param corePoolSize The maximum number of core threads in the thread pool * @param maximumPoolSize The maximum number of threads in the thread pool * @param keepAliveTime Non core thread idle timeout length * @param unit keepAliveTime Unit of * @param workQueue Task queue in thread pool */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { } /** * @param corePoolSize The maximum number of core threads in the thread pool * @param maximumPoolSize The maximum number of threads in the thread pool * @param keepAliveTime Non core thread idle timeout length * @param unit keepAliveTime Unit of * @param workQueue Task queue in thread pool * @param threadFactory Create a factory for threads */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { } /** * @param corePoolSize The maximum number of core threads in the thread pool * @param maximumPoolSize The maximum number of threads in the thread pool * @param keepAliveTime Non core thread idle timeout length * @param unit keepAliveTime Unit of * @param workQueue Task queue in thread pool * @param handler Saturation strategy */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { } /** * @param corePoolSize The maximum number of core threads in the thread pool * @param maximumPoolSize The maximum number of threads in the thread pool * @param keepAliveTime Non core thread idle timeout length * @param unit keepAliveTime Unit of * @param workQueue Task queue in thread pool * @param threadFactory Create a factory for threads * @param handler Saturation strategy */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { }
Parameter introduction:
-
int corePoolSize
The number of core threads in the thread pool. By default, core threads will uniformly survive in the thread pool, even if they are idle. If the allowCoreThreadTimeOut property of ThreadPoolExecutor is set to true, the idle core thread will have a timeout policy when waiting for the arrival of a new task. This time interval is specified by keepAliveTime. When the waiting time exceeds the time specified by keepAliveTime, the core thread will be terminated. -
int maximumPoolSize
The maximum number of threads that the thread pool can hold. When the number of active threads reaches this value, subsequent new tasks will be blocked. -
long keepAliveTime
The timeout length when the non core thread is idle. If it exceeds this length, the non core thread will be recycled. When the allowCoreThreadTimeOut property of ThreadPoolExecutor is set to true, keepAliveTime will also act on the core thread. -
TimeUnit unit
Used to specify the time unit of the keepAliveTime parameter, which is an enumeration. -
BlockingQueue workQueue
For the task queue in the thread pool, the Runnable object submitted through the execute method of the thread pool will be stored in this queue. -
ThreadFactory threadFactory
Thread factory, which provides the function of creating new threads for thread pool. ThreadFactory is an interface with only one method: Thread newThread(Runnable r). The source code is as follows:
/** * An object that creates new threads on demand. Using thread factories * removes hardwiring of calls to {@link Thread#Thread(Runnable) new Thread}, * enabling applications to use special thread subclasses, priorities, etc. * * <p> * The simplest implementation of this interface is just: * <pre> {@code * class SimpleThreadFactory implements ThreadFactory { * public Thread newThread(Runnable r) { * return new Thread(r); * } * }}</pre> * * The {@link Executors#defaultThreadFactory} method provides a more * useful simple implementation, that sets the created thread context * to known values before returning it. * @since 1.5 * @author Doug Lea */ public interface ThreadFactory { /** * Constructs a new {@code Thread}. Implementations may also initialize * priority, name, daemon status, {@code ThreadGroup}, etc. * * @param r a runnable to be executed by new thread instance * @return constructed thread, or {@code null} if the request to * create a thread is rejected */ Thread newThread(Runnable r); }
- RejectedExecutionHandler handler
Saturation strategy. When the thread pool cannot execute a new task, it may be because the task queue is full or the task cannot be executed successfully. At this time, the ThreadPoolExecutor will call the void rejectedExecution(Runnable r, ThreadPoolExecutor executor) method of the handler to notify the caller. By default, rejectedExecution() Method will directly throw a RejectedExecutionException exception. The source code of RejectedExecutionHandler is as follows:
/** * A handler for tasks that cannot be executed by a {@link ThreadPoolExecutor}. * * @since 1.5 * @author Doug Lea */ public interface RejectedExecutionHandler { /** * Method that may be invoked by a {@link ThreadPoolExecutor} when * {@link ThreadPoolExecutor#execute execute} cannot accept a * task. This may occur when no more threads or queue slots are * available because their bounds would be exceeded, or upon * shutdown of the Executor. * * <p>In the absence of other alternatives, the method may throw * an unchecked {@link RejectedExecutionException}, which will be * propagated to the caller of {@code execute}. * * @param r the runnable task requested to be executed * @param executor the executor attempting to execute this task * @throws RejectedExecutionException if there is no remedy */ void rejectedExecution(Runnable r, ThreadPoolExecutor executor); }
ThreadPoolExecutor provides four optional values for RejectedExecutionHandler: AbortPolicy, CallerRunsPolicy, DiscardPolicy and DiscardOldestPolicy. AbortPolicy is the default value and will directly throw a RejectedExecutionException exception. Detailed explanation:
-
AbortPolicy
This policy is the default rejection policy. This policy will throw an unchecked RejectedExecutionException. We can catch this exception and handle it accordingly according to our needs -
CallerRunsPolicy
Run the rejected task directly in the calling thread of the execute method, unless the executor is closed, in which case the task will be discarded. -
DiscardPolicy
It silently discards rejected tasks. -
DiscardOldestPolicy
Discard the oldest unprocessed request and retry execute.
Rules for performing tasks
- If the number of threads in the thread pool does not reach the number of core threads, a core thread will be started directly to execute the task.
- If the number of threads in the thread pool has reached or exceeded the number of core threads, the task will be inserted into the task queue and queued for execution.
- If the task cannot be inserted into the task queue in step 2, it is often because the queue task is full. At this time, if the number of threads does not reach the maximum specified in the thread pool, a non core thread will be started immediately to execute the task.
- If the number of threads in step 3 has reached the maximum specified by the thread pool, the task will be rejected, and the ThreadPoolExecutor will call the rejectedExecution() method of RejectedExecutionHandler to notify the caller.
Classification of thread pools
As mentioned above, the factory method provided by Executors can quickly create thread pools for use, so the Executors of JDK 1.8 can be summarized as providing the creation of six thread pools:
[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-zjjjuzae-16341115316459)( https://upload-images.jianshu.io/upload_images/27041669-b3b4224a2e45091d.png?imageMogr2/auto -orient/strip%7CimageView2/2/w/1240)]
- FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
This is a thread pool with a fixed number of threads. When threads are idle, they will not be recycled unless the thread pool is closed. When all threads are active, the new task will wait until a thread is idle. Because FixedThreadPool has only core threads and these core threads will not be recycled, it means that it can respond to external requests more quickly. In addition, there is no size limit on the task queue.
- CachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
This is a thread pool with an indefinite number of threads. The thread pool has only non core threads, and the maximum number of threads is Integer.MAX_VALUE. When all threads in the thread pool are active, the thread pool will create new threads to process new tasks, otherwise idle threads will be used to process new tasks. All idle threads in the thread pool have a timeout mechanism. The timeout time is 60 seconds. After that, idle threads will be recycled. Different from FixedThreadPool, the task queue of CachedThreadPool is actually an empty collection, which will cause any task to be executed immediately, because in this scenario, synchronous queue cannot insert tasks. Synchronous queue is a very special queue. In many cases, it can be simply understood as a queue that cannot store elements. This thread pool is more suitable for performing a large number of less time-consuming tasks. When the whole thread pool is idle, the threads in the thread pool will timeout and be stopped. At this time, there are actually no task threads in the CachedThreadPool, which hardly occupies any system resources.
- ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }
This is a thread pool in which the number of core threads is fixed, but the number of non core threads is unlimited, and will be recycled immediately when non core threads are idle. The thread pool is mainly used to perform scheduled tasks and repetitive tasks with fixed cycles.
- SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
This is a thread pool with only one thread inside. It ensures that all tasks are executed sequentially in the same thread. The significance of the thread pool is to unify all external tasks into one thread, which makes it unnecessary to deal with the problem of thread synchronization between these tasks.
- SingleThreadScheduledExecutor
public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); } public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1, threadFactory)); }
This is a single thread pool that can execute tasks periodically.
WorkStealingPool public static ExecutorService newWorkStealingPool(int parallelism) { return new ForkJoinPool (parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); } public static ExecutorService newWorkStealingPool() { return new ForkJoinPool (Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); }
This is the thread pool introduced by JDK1.8. The thread pool uses all available processors as the target parallelism to create a pool for stealing threads and has multiple task queues. One task can produce other smaller tasks, which are added to the queue of parallel processing threads. If one thread completes its work and has nothing to do, it can "steal" work from the queue of the other thread. This thread pool does not guarantee the sequential execution of tasks because it works preemptively. The thread pool is mainly implemented by ForkJoinPool, which is based on work steaming algorithm.