“This is the fourth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

preface

Figure: Alibaba Java development manual has clear specifications for the creation of thread pools. The thread pool returning from Executors has an unavoidable disadvantage. Use a thread pool to force ThreadPoolExecutor to be created. You are advised to use a thread pool if you have a good understanding of the mechanism.

Of course, there are other reasons to use ThreadPoolExecutor to create thread pools:

  1. Manually configure thread pool parameters such as number of core threads, task queues used, rejection policies, and so on, based on machine performance and business scenarios.
  2. Name our thread pool explicitly to help locate the problem.
  3. It makes it easier for developers to monitor thread pool health and adjust policies to avoid production problems.

Here is a case of an online accident where thread pools were used improperly. The case is brilliant, the scene is realistic. This is a good example of a thread pool problem.

PS: If you don’t want to see my summary: the parent task depends on the child task execution, and is executed in the same thread pool newFixedThreadPool, resulting in the core thread deadlock unbounded queue task increasing.

The body of the

Since thread pools are very common knowledge in daily work and need to be fully aware of this in the process of use, so today we summarize the common knowledge of thread pools.

Simple example

The following implements a custom thread pool to perform Callable tasks using ThreadPoolExecutor:

class ImpCallable implements Callable<String> {
    // Number of core threads
    private static final int CORE_POOL_SIZE=2;
    // Maximum number of threads
    private static final int MAX_POOL_SIZE=4;
    // Number of threads greater than corePoolSize Thread duration
    private static final int KEEP_ALIVE_TIME=1;
    // Size of the blocking queue
    private static final int QUEUE_CAPACITY=5;
    private static final TimeUnit UNIT = TimeUnit.SECONDS;
    // Custom thread name
    private static final String THREAD_NAME = "my-self-thread-pool-%d";

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                UNIT,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new BasicThreadFactory.Builder().namingPattern(THREAD_NAME).build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        ImpCallable task = new ImpCallable();
        List<Future<String>> futureList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            // Submit the task to the thread pool
            Future<String> future = executor.submit(task);
            // Task result future joins the result queue
            futureList.add(future);
        }

        for (Future<String> fut : futureList) {
            try {
                // Retrieve the result
                System.out.println(new Date() + "--" + fut.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        executor.shutdown();

        try {
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All threads Finished");
    }

    @Override
    public String call(a) throws Exception {
        Thread.sleep(2000L);
        returnThread.currentThread().getName(); }}Copy the code

Blocking queue

A blocking queue used to hold tasks waiting to be executed. Java provides the following blocking queue:

  1. ArrayBlockingQueue: a bounded blocking queue based on an array structure whose construction must specify the size. Objects are sorted by FIFO.
  2. LinkedBlockingQuene: block queue based on linked list structure. The size is not fixed. If the size is not specified, the size isInteger.MAX_VALUE To decide. Objects are sorted by FIFO. Throughput is usually higher thanArrayBlockingQuene.
  3. SynchronousQuene: a specialBlockingQueue, the operation must be completed alternately by putting and fetching.
  4. priorityBlockingQuene: similar toLinkedBlockingQueueObjects are sorted by the natural order of objects or by the constructorComparatorDecision.

Custom thread name

Create a thread factory, through the custom thread factory can be set for each new thread with a recognition of the thread name.

This is necessary to quickly locate problems and monitor thread pools.

Rejection policies

The saturation strategy of the thread pool. When the blocking queue is full and there are no idle worker threads, if the task continues to be submitted, a policy must be adopted to process the task. The thread pool provides four strategies:

  1. AbortPolicyThrow an exception directly,The default policyTo detect thread pool bottlenecks in a timely manner.
  2. CallerRunsPolicyUsing the caller’s thread to execute the task, the new task will not be lost, adopt the policy of who submits who is responsible, effectively control the thread pool pressure.
  3. ,DiscardOldestPolicyDiscards the first task in the blocking queue and executes the current task. This policy has the risk of task loss.Not recommended.
  4. DiscardPolicyJust drop the task. This strategy is simple and crude to ensure that the system can be the primary target,Not recommended.

You can also implement the RejectedExecutionHandler interface to customize saturation policies based on application scenarios. Tasks that cannot be handled by logging or persistent storage.

Thread pool size

The thread pool size can be set based on actual service scenarios.

Recommended a more widely applicable formula (N is the number of CPU cores) :

  • CPU intensive task N+1.
  • IO intensive task 2N.

Thread pool state

As shown above, thread pool states are divided into five types, corresponding to the five int fields in Java respectively:

private static final int RUNNING = -536870912;
private static final int SHUTDOWN = 0;
private static final int STOP = 536870912;
private static final int TIDYING = 1073741824;
private static final int TERMINATED = 1610612736;
Copy the code
  1. The RUNNING thread has successfully created the initialization state and is able to receive new tasks and process added tasks.

  2. SHUTDOWN does not accept new tasks, but can process added tasks.

  3. STOP does not receive new tasks, does not process added tasks, and interrupts ongoing tasks.

  4. TIDYING The thread pool becomes TIDYING when all tasks have terminated and the queue task is empty.

  5. The TERMINATED thread pool is TERMINATED completely from the TIDYING state to the TERMINATED state.

Monitoring thread pools

We can monitor the running status of thread pools using third-party components such as those in SpringBoot.

In addition, we can use the related API of ThreadPoolExecutor to monitor thread pool state. As shown below, we can easily obtain various parameters of the thread pool and monitor the thread pool health status in real time by combining with mail.

Here we recommend the implementation principle of Meituan Java thread pool and its practice in Meituan business.

In the article of Thread pool of Meituan, dynamic configuration of core parameters of thread pool, health monitoring and alarm of thread pool, centralized management of thread pool are realized. You can do your own research if you are interested.

The resources

  • Java thread pool rejection policy
  • Better use of JAVA thread pools
  • Several states of Java thread pools
  • Implementation principle of Java thread pool and its practice in Meituan business
  • Three ways to set a thread name by creating a ThreadFactory in a Java thread pool