From the school to A factory all the way sunshine vicissitudes of life

Please go to www.codercc.com


1. Why use thread pools

In practice, threads are a resource hog, and can easily lead to system problems if threads are poorly managed. Therefore, thread pools are used to manage threads in most concurrency frameworks. The main benefits of using thread pools to manage threads are as follows:

  1. Reduce resource consumption. The system performance loss can be reduced as much as possible by reusing existing threads and reducing the closing times of threads.
  2. Improve system response speed. By multiplexing threads, the process of creating threads is omitted, so the overall response speed of the system is improved.
  3. Improve thread manageability. Threads are scarce resources. If created without limit, they will consume system resources and reduce system stability. Therefore, thread pools are needed to manage threads.

2. How thread pools work

When a concurrent task is submitted to a thread pool, the thread pool allocates threads to execute the task as shown below:

Thread pool execution flowchart.jpg

As can be seen from the figure, the process of thread pool executing the submitted task mainly has the following stages:

  1. Check whether all threads in the core thread pool are executing tasks. If not, create a new thread to execute the submitted task. Otherwise, all threads in the core thread pool are executing the task, and go to Step 2.
  2. Determine whether the current blocking queue is full, if not, put the submitted task in the blocking queue; Otherwise, go to step 3;
  3. Determine whether all threads in the thread pool are executing a task. If not, create a new thread to execute the task; otherwise, hand over the task to the saturation policy

3. Create a thread pool

Creating a thread pool is primarily done by the ThreadPoolExecutor class. ThreadPoolExecutor has a number of overloaded constructors, using the constructor with the most arguments to understand what parameters need to be configured to create a thread pool. ThreadPoolExecutor is constructed by:

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

The following describes the parameters:

  1. CorePoolSize: Indicates the size of the core thread pool. When a task is submitted, if the number of threads in the current core thread pool does not reach corePoolSize, a new thread is created to execute the submitted task,Even if there are currently idle threads in the core thread pool. If the number of threads in the current core thread pool has reached corePoolSize, the thread will not be recreated. If it’s calledprestartCoreThread()orprestartAllCoreThreads()All core threads are created and started when the thread pool is created.
  2. MaximumPoolSize: indicates the maximum number of threads that can be created in a thread pool. If the blocking queue is full and the number of threads in the current thread pool does not exceed maximumPoolSize, a new thread is created to execute the task.
  3. KeepAliveTime: indicates the keepAliveTime of idle threads. If the number of threads in the current thread pool exceeds corePoolSize and the idle time exceeds keepAliveTime, these idle threads are destroyed to minimize system resource consumption.
  4. Unit: time unit. Specifies the time unit for keepAliveTime.
  5. WorkQueue: blocking queue. Block queues are used to save tasks. See this article about blocking queues. You can use ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue.
  6. ThreadFactory: An engineering class that creates threads. You can specify a thread factory to give each created thread a more meaningful name, making it easier to find the cause of concurrency problems if they occur.
  7. Handler: saturation policy. When the blocking queue of the thread pool is full and the specified threads are started, the current thread pool is saturated, and a strategy is needed to handle this situation. The strategies used are as follows:
    1. AbortPolicy: reject the submitted directly to the task, and throw RejectedExecutionException exception;
    2. CallerRunsPolicy: Executes the task only in the caller’s thread;
    3. DiscardPolicy: Discards the task without processing it.
    4. DiscardOldestPolicy: Discards the task with the longest duration in the blocked queue and executes the current task

Thread pools perform logic

After creating a thread pool with ThreadPoolExecutor, the task is submitted and executed. Execute method:

public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState  and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); If (workerCountOf(c) < corePoolSize) {if (addWorker(command, true)) return; c = ctl.get(); } // If the number of threads is greater than corePoolSize or the thread creation fails, If (isRunning(c) &&workqueue.offer (command)) {int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } // If the current task cannot be placed in the blocking queue, create a new thread to execute the task else if (! addWorker(command, false)) reject(command); }Copy the code

Note For the logic of executing the Execute method of ThreadPoolExecutor, see the comment. The following diagram shows the execution of ThreadPoolExecutor’s execute method:

Execute Schematic diagram of the execution process. JPG

The execution logic of the execute method is as follows:

  1. If fewer threads are currently running than corePoolSize, a new thread is created to perform the new task;
  2. If the number of threads running is equal to or greater than corePoolSize, the submitted task is placed in the blocking queue workQueue.
  3. If the current workQueue is full, a new thread is created to execute the task.
  4. If the number of threads exceeds maximumPoolSize, the saturation policy RejectedExecutionHandler will be used.

It is important to note that the thread pool is designed to use the core thread pool corePoolSize, the blocking queue workQueue, and the thread pool maximumPoolSize to process tasks. In fact, this design concept is used in the requirements framework.

4. Closing the thread pool

To shutdown the thread pool, you can use shutdown and shutdownNow. They all work by traversing all threads in a thread pool and then interrupting them in turn. There are some differences between Shutdown and shutdownNow:

  1. shutdownNowStart by setting the thread pool state toSTOPAnd then tryStop all ongoing and pending tasksAnd returns a list of tasks to be executed;
  2. shutdownSimply set the thread pool state toSHUTDOWNState, and then interrupts all threads that are not executing tasks

As you can see, the shutdown method continues the ongoing task, while shutdownNow directly interrupts the ongoing task. The isShutdown method returns true when either of the two methods is called. The thread pool is closed only when all threads have been shut down, and the isTerminated method returns true.

5. How to set thread pool parameters properly?

To properly configure thread pools, you must first analyze task characteristics, which can be analyzed from the following perspectives:

  1. Nature of tasks: CPU intensive tasks, IO intensive tasks and hybrid tasks.
  2. Task priority: high, medium and low.
  3. Task execution time: long, medium and short.
  4. Task dependencies: Whether they depend on other system resources, such as database connections.

Tasks of different nature can be handled separately by thread pools of different sizes. CPU intensive tasks are configured with as few threads as possible, such as Ncpu+1 thread pool. For IO-intensive tasks, threads do not execute tasks all the time because they need to wait for I/O operations. Therefore, configure as many threads as possible, for example, 2xNcpu. Hybrid task, if you can break up, it is split into a cpu-intensive task and an IO intensive tasks, as long as the two task execution time difference is not too big, then decomposed execution throughput than the throughput of serial execution, if the huge difference between the two task execution time, don’t need to split up. We can use runtime.getruntime ().availableProcessors() to get the number of cpus on the current device.

Tasks with different priorities can be processed using the PriorityBlockingQueue. It allows the higher-priority tasks to be executed first. Note that if higher-priority tasks are always submitted to the queue, the lower-priority tasks may never be executed.

Tasks with different execution times can be assigned to thread pools of different sizes, or priority queues can be used to allow shorter tasks to be executed first.

A task that depends on the database connection pool. If a thread submits SQL and waits for the database to return the result, the longer the wait, the longer the CPU idle time, the larger the number of threads should be set to better use the CPU.

Moreover, the blocking queue is best to use bounded queue, if the use of unbounded queue, once the backlog of tasks in the blocking queue will occupy too much memory resources, and even make the system crash.

reference

The Art of Concurrent Programming in Java

ThreadPoolExecutor source code analysis, very detailed