Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

The asynchronous execution of multithreading, although it can maximize the computing power of multi-core computers, but if not controlled, it will cause a burden on the system. Threads themselves take up Memory space, and a large number of threads can take up Memory resources and possibly cause Out of Memory. Even when this is not the case, a large number of thread collections can put a lot of pressure on the GC. To avoid creating threads repeatedly, thread pools are available to allow threads to be reused. In layman’s terms, when work comes in, a thread is taken from the thread pool, and when the work is done, instead of closing the thread, it is returned to the thread pool for use by other tasks.

Overall thread pool structure

Executor is the top-level interface of the thread pool. This interface has a core method, Execute (Runnable Command), which is implemented by the ThreadPoolExecutor class and is used to execute tasks. The ExecutorService interface inherits Executor and provides methods for shutting down and executing threads, such as shutdown(),shutdownNew(), and Submit ().

The final default implementation of the ExecutorService class is ThreadPoolExecutor.

ThreadPoolExecutor analysis

The first step is through the constructor.

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

Copy the code
  • CorePoolSize: the corePoolSize of the thread pool. After the thread pool is created, there are no threads in the pool by default. After the pool is created, the default pool number of threads is 0

  • MaximumPoolSize: indicates the maximum number of threads

  • WorkQueue A blocking queue used to store tasks waiting to be executed. When the number of threads in the thread pool exceeds its corePoolSize, the thread enters the blocking queue. With workQueue, thread pools implement blocking

  • ThreadFactory: threadFactory, used to create threads

  • Handler: Indicates the policy for refusing to process a task

  • KeepAliveTime: The thread pool maintains the idle time allowed by the thread lock. When the number of threads in the thread pool is greater than corePoolSize, if no new tasks are submitted at this time, threads outside the core thread will not be destroyed immediately, but will wait until the keepAliveTime is exceeded

    How do these parameters come into play in the implementation, let’s see

Process for executing execute

! [images] (https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8a3814f21d114d18b070492fe904360d~tplv-k3u1fbpfcp-zoom-1.image)Copy the code

When the task arrives, a new thread is created until corePoolSize is reached, and the thread is put into the workQueue. If the running thread is smaller than corePoolSize, even if there are idle threads, If the thread pool is larger than corePoolSize but smaller than maximumPoolSize, new threads are created to process the task only when the workQueue is full

If the number of threads running is greater than or equal to maximumPoolSize, and the workQueue is already full, it is processed by the handler’s specified policy. If threads other than the core thread do not process tasks and exceed keepAliveTime, it is recycled.

There are four common thread pools

newCachedThreadPool

   public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
Copy the code
  • There is almost no limit to the number of worker threads that can be created (although there is a limit to the number of interger.max_value), giving you the flexibility to add threads to the thread pool.

  • If a task has not been submitted to the thread pool for a long time, that is, if a worker thread is idle for a specified period of time (default: 1 minute), the worker thread is automatically terminated. After termination, if you submit a new task, the thread pool creates a new worker thread.

  • When using CachedThreadPool, it is important to control the number of tasks, otherwise the system will crash due to a large number of threads running at the same time.

newFixedThreadPool

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

Copy the code
  • Creates a thread pool that specifies the number of worker threads. A thread is created each time a task is submitted, and if the number of worker threads reaches the initial maximum of the thread pool, the submitted task is deposited to the thread pool queue.

  • A FixedThreadPool is a typical and excellent thread pool that has the advantages of increasing program efficiency and saving overhead when creating threads. However, when the thread pool is idle, that is, when there are no runnable tasks in the thread pool, it does not release worker threads and consumes some system resources.

 

newSingleThreadExecutor

  public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>()));    }
Copy the code

A single-threaded thread pool, with only one thread working (if this unique thread terminates due to an exception, a new thread will replace it). Ensure that all tasks are executed in the same order as the tasks submitted, and only one task is running at the same time.

newScheduleThreadPool

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

Copy the code

The return value is ScheduledExecutorService

Create a fixed-length thread pool and support timed and periodic task execution. Thread pools that can run regularly (initial latency), run frequently (how often do they run, or how often do they run again after a successful run) are good for timed and periodic tasks.

There is one execution method that needs to be noticed.

private void threadPoolExecutorTest6() throws Exception { ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5); Thread pooling provides two scheduling methods for periodically executing a task, which are shown separately here. The test scenario is the same. // Test scenario: The submitted task takes 3 seconds to complete. Check the differences between the two scheduling modes // Effect 1: After the submission, the first execution starts 2 seconds later. After the submission, the execution is performed at a fixed interval of one second. (If the last execution is not completed, the execution is completed immediately.) // Execute every 3 seconds. Each execution takes three seconds with an interval of one second. The next execution starts immediately after the execution is complete. Without waiting for) threadPoolExecutor. ScheduleAtFixedRate (new Runnable () {@ Override public void the run () {try {thread.sleep (3000 l); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" task-1 is executed, now time: "+ system.currentTimemillis ()); } }, 2000, 1000, TimeUnit.MILLISECONDS); // Effect 2: after the submission, the first execution will start 2 seconds later, and the execution will be fixed once every 1 second after the submission (if the last execution is not completed, the execution will be completed, and the timing will start after the last execution, and wait 1 second). // This code clock looks like it executes every 4 seconds. (Calculation method: Each execution takes 3 seconds with an interval of 1 second. After the execution, wait another 1 second. So is 3 + 1) threadPoolExecutor scheduleWithFixedDelay (new Runnable () {@ Override public void the run () {try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (" task-2 is executed, now time: "+ system.currentTimemillis ()); } }, 2000, 1000, TimeUnit.MILLISECONDS); }Copy the code

Blocking queue

In a thread pool, if the number of tasks exceeds the number of core threads, they are put into a blocking queue for execution. There are three main types of blocking queues in the thread pool

  • ArrayBlockingQueue: Array-based bounded queue.

  • LinkedBlockingQueue: A first in, first out queue based on a linked list, which is unbounded.

  • SynchronousQueue: Unbuffered wait queue that delegates tasks directly to threads without holding them. If there are no threads available to run the task immediately (that is, all the threads in the thread pool are working), an attempt to add the task to the buffered queue will fail, so a new thread is constructed to handle the newly added task and add it to the thread pool.

Four rejection strategies

  • AbortPolicy discarded tasks, and throw RejectedExecutionException anomalies

  • CallerRunsPolicy: This task is rejected by the thread pool and is executed by the thread calling the execute method.

  • DiscardOldestPolicy: Discards the task at the top of the queue and retry the task.

  • DiscardPolicy: Discards the task, but does not throw an exception.