Say what I said before

The purpose of using multithreading

  1. With multiple CPU cores, the benefits of multithreading are obvious, otherwise multiple CPU cores run only one thread and the rest of the core is wasted

  2. Even without multi-core, multi-threading makes sense on a single core, because when an operation, such as an IO operation, is blocked, the CPU does not need to participate in it. At this point, the CPU can open another thread to do something else, wait for the IO operation to complete and then return to the previous thread to continue executing

Why use thread pools, and what benefits do we have?

  1. Reduced resource consumption: Reduces thread creation and destruction costs by reusing created threads

  2. Speed up: When a task arrives, it can be executed immediately without waiting for the thread to be created

  3. Improve manageability of threads: Threads are scarce resources. If created without limit, they will consume resources and reduce system stability. Thread pools can be used for uniform allocation, tuning, and monitoring.

The risks of using thread pools

  1. A deadlock

  2. Insufficient resources

  3. Concurrent error

  4. Thread leakage

  5. Request overload

Use of thread pools

We can create a thread pool using ThreadPoolExecutor.

newThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
Copy the code

To create a thread pool, enter several parameters:

  • CorePoolSize (base size of thread pool) : When a task is submitted to the thread pool, the thread pool creates a thread to execute the task, even if other free base threads are able to execute new tasks, until the number of tasks that need to be executed exceeds the base size of the thread pool. If the thread pool’s prestartAllCoreThreads method is called, the pool creates and starts all base threads ahead of time.

  • RunnableTaskQueue: A blocking queue that holds tasks waiting to be executed. There are several blocking queues to choose from:

ArrayBlockingQueue: ArrayBlockingQueue is a bounded blocking queue based on an array structure that sorts elements in FIFO(first-in, first-out) order.

LinkedBlockingQueue: A blocking queue based on a linked list structure that sorts elements in FIFO(first in, first out) and typically has a higher throughput than ArrayBlockingQueue. Static factory methods Executors. NewFixedThreadPool () using the queue. SynchronousQueue: A blocking queue that does not store elements. Each insert operation must wait for another thread calls to remove operation, otherwise the insert has been in the blocking state, throughput is generally higher than LikedBlockingQueue, static factory methods Executor. NewCachedThreadPool using the queue. PriorityBlockingQueue: An infinite blocking queue with a priority.

  • MaximumPoolSize: The maximum number of threads allowed to be created in a thread pool. If the queue is full and the number of threads created is less than the maximum, the thread pool creates a new thread to execute the task. Note that this parameter has no effect if the unbounded task queue is used.

  • ThreadFactory: Used to set up a factory for creating threads. You can use the ThreadFactory to give each thread a more interesting name. Useful for debugging and locating problems.

  • RejectedExecutionHandler(saturation policy) : When both queues and threads are full, the thread pool is saturated and a policy must be adopted to handle submitted new tasks. This policy is AbortPolicy by default, indicating that an exception is thrown when a new task cannot be processed. The following four strategies are available for JDK1.5.

AbortPolicy: Directly throws an exception. CallerRunsPolicy: Only call the thread of the philosopher to run the task. DiscardOldestPolicy: Discards the most recent task in the queue and executes the current task. DiscardPolicy: Do not process, discard. The RejectedExecutionHandler interface can also be used to customize a policy based on application scenarios. Such as logging or persisting tasks that cannot be processed.

  • KeepAliveTime: The amount of time that a worker thread in a thread pool stays alive after it is idle. Therefore, if there are many tasks and the execution time of each task is short, you can increase the duration to improve thread utilization.

  • TimeUnit(a unit of hold time for thread activity) : The options are in DAYS, HOURS, MINUTES, MILLISECONDS, MICROSECONDS and NANOSECONDS.

Submit tasks to the thread pool

We can use execute to submit a task, but the execute method does not return a value, so we cannot determine whether the task was successfully executed by the thread pool. The following code shows that the task entered by the execute method is an instance of the Runnable class.

We can also use the Submit method to submit the task, and it will return a future, so we can use the future to determine whether the task was successfully executed, and use the Future’s get method to get the return value, the GET method will block until the task is complete, Using get(long timeout,TimeUnit Unit) will block for a while and then return immediately. In this case, the task may not have finished.

Closing the thread pool

We can shutdown the thread pool by calling the shutdown or shutdownNow methods of the thread pool, but they work differently. Shutdown simply sets the state of the thread pool to shutdown, and then interrupts all threads that are not executing tasks. ShutdownNow works by iterating through worker threads in a thread pool and then interrupting them one by one by calling the thread_interrupt method, so tasks that cannot respond to interrupts may never be terminated. ShutdownNow first sets the state of the thread pool to STOP, then attempts to STOP all threads executing or suspending tasks and returns a list of tasks awaiting execution. The isShutdown method returns true whenever either of the two shutdown methods is called. The thread pool is closed successfully when all tasks are closed, and calling isTerminaed returns true. Which method we should call to shutdown the thread pool depends on the nature of the task submitted to the thread pool. Shutdown is usually called to shutdown the thread pool, or shutdownNow if the task is not necessarily finished.

Implementation principle of thread pool

Flow analysis: The main workflow of the thread pool is shown below:

Configure thread pools 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, 2Ncpu. 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 two task execution time difference is too big, it should not be necessary to break down. 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 priority tasks to be executed first. Note that if there are always higher priority tasks 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. It is recommended to use the bounded queue. The bounded queue can increase the stability and warning ability of the system. It can be set as larger as required, such as thousands. Once we set USES the background task queue of the thread pool and thread pool is full, the task of constantly thrown away, is found by screening the database problems, lead to execute SQL becomes very slowly, because the background tasks all the tasks in the thread pool is need to the database query and insert the data, so the cause of the work in the thread pool threads blocked entirely, Tasks are pinned in a thread pool. If we had set the queue to unbounded, the thread pool would have become so large that it would have overwhelmed memory and rendered the entire system unusable, not just the background tasks. Of course, all tasks on our system are deployed on a separate server, and we use thread pools of different sizes to run different types of tasks, but problems like this can affect other tasks as well.

Here I recommend an architecture learning exchange group. Exchange learning group number: 575745314 inside will share some senior architects recorded video video: Spring, MyBatis, Netty source analysis, high concurrency, high performance, distributed, microservice architecture principle, JVM performance optimization, distributed architecture, and so on these become architects necessary knowledge system. I can also get free learning resources, which I benefit a lot from now

Thread pool monitoring

Monitored by the parameters provided by the thread pool. There are several properties in the thread pool that you can use when monitoring the thread pool

  • TaskCount: Indicates the number of tasks that need to be executed by the thread pool

  • CompletedTaskCount: The number of tasks completed by the thread pool during a run. Less than or equal to taskCount.

  • LargestPoolSize: maximum number of threads ever created by the thread pool. This data lets you know if the thread pool is full. If equal to the maximum size of the thread pool, it indicates that the thread pool was once full.

  • GetPoolSize: specifies the number of threads in the thread pool. Threads in the pool will not destroy themselves if the pool is not destroyed, so the size will only increase.

  • GetActiveCount: Gets the number of active threads.

Monitor by extending the thread pool. By inheriting the thread pool and overriding the beforeExecute, afterExecute, and terminated methods of the thread pool, we can do things before, after, and before the thread is terminated. For example, monitor the average, maximum, and minimum execution time of a task. These methods are empty methods in the thread pool.