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

With the problem

  1. Why doesn’t the Java code specification allow you to quickly create thread pools using Executors?
  2. What is the following code output?
ThreadPoolExecutor executor = new ThreadPoolExecutor(
        1, //corePoolSize
        100, //maximumPoolSize
        100, //keepAliveTime
        TimeUnit.SECONDS, //unit
        new LinkedBlockingDeque<>(100));//workQueue

for (int i = 0; i < 5; i++) {
    final int taskIndex = i;
    executor.execute(() -> {
        System.out.println(taskIndex);
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}
Copy the code

A) 0 1 2 3 4 5 B) 0~5 inconsistent sequence output 5 lines C) 0

Return to the directory

basis

What is a thread pool?

A thread pool can be seen as a set of resources, and any pool serves the same purpose, mainly to reduce the overhead of resource creation and initialization.

Is thread creation “expensive”?

Yes. Threads are expensive to create.

We all know that each process in the system has its own independent memory space, and threads, called lightweight processes, are also needed.

By default in the JVM a thread needs to use 256K to 1M memory (depending on the 32-bit or 64-bit operating system). (We won’t go into the details of the array, because the default value can change from JVM version to JVM version at any time, but we need to know that threads need to consume memory.)

Is there more than memory? Many articles will include context switching and CPU scheduling. Thread scheduling is not included here because sleeping threads are not scheduled (OS controlled), and if not sleeping threads must be scheduled. However, in addition to the memory consumption at creation time in the JVM, there is also pressure on the GC, and if threads are created frequently, they will need to be reclaimed during the relative GC.

Thread pool mechanism?

It can be seen that thread pool is a kind of technology to reuse threads. The main mechanism of thread pool is to keep a certain number of threads to sleep when there is nothing to do, and to take a thread to run when there is work to do. These involve specific policies for thread pool implementation.

What other common pools are there?

  • The thread pool
  • Connection pooling (database connections, TCP connections, and so on)
  • BufferPool
  • .

Thread pools in Java

UML Diagrams (Java 8)

You can see that the actual implementation class has

  1. ThreadPoolExecutor (1.5)
  2. ForkJoinPool (1.7)
  3. ScheduledThreadPoolExecutor (1.5)

Today we’ll focus on ThreadPoolExecutor as one of the more used implementations.

Executors provide factory methods

  1. newCachedThreadPool (ThreadPoolExecutor)

    Create a cacheable thread pool. If the size of the thread pool exceeds the number of threads needed to process the task, the pool can reclaim some of the idle (60-second non-executing) threads and intelligently add new threads to process the task as the number of tasks increases. This thread pool has no limit on the thread pool size, which is entirely dependent on the maximum thread size that the operating system (or JVM) can create.

  2. newFixedThreadPool (ThreadPoolExecutor)

    Create a thread pool of fixed size. A thread is created each time a task is submitted until the thread reaches the maximum size of the thread pool. The size of the thread pool stays the same once it reaches its maximum size, and if a thread terminates due to execution exceptions, a new thread is added to the pool.

  3. newSingleThreadExecutor (ThreadPoolExecutor)

    Create a single thread pool. This thread pool has only one thread working, which is equivalent to a single thread executing all tasks in serial. If the unique thread terminates due to an exception, a new thread will replace it. This thread pool ensures that all tasks are executed in the order in which they were submitted.

  4. newScheduledThreadPool (ScheduledThreadPoolExecutor)

    Create a thread pool of unlimited size. This thread pool supports the need to execute tasks regularly and periodically.

  5. newSingleThreadScheduledExecutor (ScheduledThreadPoolExecutor)

    The requirement to create a single thread to perform tasks periodically and periodically.

  6. ForkJoinPool newWorkStealingPool (1.8)

    Create a job steal

You can see that there are only three thread pool implementation classes used in the various factory methods, as follows:

The factory method The implementation class
newCachedThreadPool ThreadPoolExecutor
newFixedThreadPool ThreadPoolExecutor
newSingleThreadExecutor ThreadPoolExecutor
newScheduledThreadPool ScheduledThreadPoolExecutor
newSingleThreadScheduledExecutor ScheduledThreadPoolExecutor
newWorkStealingPool ForkJoinPool

ThreadPoolExecutor

First let’s look at the full constructor of ThreadPoolExecutor

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

    Unless allowCoreThreadTimeOut is set, this is the minimum number of threads in the pool that should remain in the pool even if they exceed idle time.

    Note that the thread required for corePoolSize is not created immediately and needs to be created after the submission of the task. Therefore, if there is a large number of cache threads, you can submit an empty task and let the thread pool create the thread first, thus improving the efficiency of subsequent execution.

  2. maximumPoolSize

    Maximum number of threads allowed.

  3. keepAliveTime

    Idle Thread Idle lifetime. The core thread needs allowCoreThreadTimeOut to be true to exit.

  4. unit

    Together with keepAliveTime, set the unit of keepAliveTime, such as milliseconds and seconds.

  5. workQueue

    A task queue in a thread pool. As mentioned above, the main purpose of a thread pool is to reuse threads to process tasks, so we need a queue to hold the tasks that need to be executed, and to use the threads in the pool to process those tasks, we need a task queue.

  6. threadFactory

    A thread is created through a thread project when the thread pool determines that a new thread is needed.

  7. handler

    Handler that the thread pool cannot handle when execution is blocked. This is related to the task queue. For example, the queue size can be specified in the queue. What if the queue size is exceeded? The JDK has taken this into account for us and provides four default implementations.

    Here are the policies that come with the JDK by default:

    1. AbortPolicy (default)

    Throw RejectedExecutionException anomalies.

    1. CallerRunsPolicy

    Call the thread of the current thread pool to execute.

    1. DiscardPolicy

    The current task is discarded.

    1. DiscardOldestPolicy

    Discard the oldest task and add the current task to the queue.

Confusing argument: corePoolSize maximumPoolSize workQueue

Logical relationship between task queue, number of core threads and maximum number of threads

  1. When the number of threads is smaller than the number of core threads, a thread is created.

  2. When the number of threads is greater than or equal to the number of core threads and the task queue is not full, the task is put into the task queue.

  3. When the number of threads is greater than or equal to the number of core threads and the task queue is full

    1. If the number of threads is smaller than the maximum number, create a thread
    2. If the number of threads is equal to the maximum number of threads, call the reject handler (default effect: throw exception, reject task)

What are the recommended Settings for these three parameters? Is there an optimal value?

Because Java’s support for coroutines is unfriendly, it relies heavily on thread pools and threads. Therefore, this value has no optimal recommendation and needs to be set according to business requirements. Different requirement types can create multiple different thread pools to execute.

Q1: Why does ali development specifications not allow Executors to create thread pools quickly?

As you can see, the reason is simple

  1. newSingleThreadExecutor
  2. newFixedThreadPool

Using new LinkedBlockingQueue

() directly in the workQueue argument can theoretically add tasks to the thread pool indefinitely.

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

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

If the task submitted to the thread pool is caused by a problem such as sleep permanence, it will cause a memory leak and eventually result in OOM.

Ali also recommends customizing threadFactory with thread names for troubleshooting later.

Question 2: What is the following code output?

That’s choice C. Although the maximum number of threads is 100, the number of core threads is 1, and the task queue is 100. If the number of threads is greater than or equal to the number of core threads and the task queue is not full, the task is put into the task queue. ‘This condition. As a result, subsequent tasks will be blocked.