Thread Pool in Java

The thread pool in java is the most used concurrent framework, and almost all programs that need to execute tasks asynchronously or concurrently can use the thread pool. In the development process, rational use of thread pools can bring three benefits.

First: reduce resource consumption. Reduce the cost of thread creation and destruction by reusing already created threads.

Second: improve the response speed. When a task arrives, the task can be executed immediately without waiting for the thread to be created.

Third: improve the manageability of threads. Threads are scarce resources. If they are created without restrictions, it will not only consume system resources, but also reduce the stability of the system. Using thread pools can be used for unified allocation, tuning, and monitoring. But to make reasonable use of the thread pool, you must know its principles well.

Implementation principle of thread pool

When a task is submitted to the thread pool, how does the thread pool handle the task?

1. The thread pool determines whether the threads in the core thread pool are all executing tasks. If not, create a new worker thread to execute the task. If the threads in the core thread pool are all executing tasks, enter the next process.

2. The thread pool determines whether the work queue is full. If the work queue is not full, store newly submitted tasks in this work queue. If the work queue is full, go to the next process.

3. The thread pool judges whether the threads in the thread pool are all in working state. If not, a new worker thread is created to execute the task. If it is full, hand it over to the saturation strategy to handle the task

The main process of the thread pool:

ThreadPoolExecutor execution diagram

ThreadPoolExecutor executes the Execute method in the following four cases.

1. If there are fewer threads currently running than corePoolSize, create a new thread to execute the task (note that this step requires acquiring a global lock).

2. If the running thread is equal to or more than corePoolSize, add the task to the BlockingQueue.

3. If the task cannot be added to the BlockingQueue (the queue is full), create a new thread to process the task (note that a global lock is required to perform this step).

4. If the new thread created will cause the current thread to exceed the maximumPoolSize, the task will be rejected and the RejectedExecutorHandler.rejectedExecution() method will be called.

The overall design idea of ​​ThreadPoolExecutor taking the above steps is to avoid acquiring the global lock as much as possible when executing the execute() method (that would be a serious scalability bottleneck). After the ThreadPoolExecutor completes warm-up (the number of currently running threads is greater than or equal to corePoolSize), almost all execute() method calls are performed in step 2, and step 2 does not need to acquire a global lock

Source code analysis: The method of executing tasks in the thread pool is as follows:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
      
        int c = ctl.get();
        //如果线程数小于核心线程数,则闯进线程并执行当前任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果线程数大于等于核心线程数或创建线程失败,则将当前任务放到工作队列中
        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);
        }
        //抛出RejectedExcutionException异常
        else if (!addWorker(command, false))
            reject(command);
    }

Worker thread: When the thread pool creates a thread, it will encapsulate the thread into a worker thread Worker. After the Worker completes the task, it will also cyclically obtain the tasks in the work queue for execution. We can see this from the run() method of the Worker class.

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

Use of thread pool

Thread pool creation

We can create a thread pool through ThreadPoolExecutor.

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

1. corePoolSize (basic size of the thread pool): When a task is submitted to the thread pool, the thread pool will create a thread to execute the task, even if other idle basic threads can execute new tasks, threads will be created, waiting for the task to be executed When the number is greater than the basic size of the thread pool, it will not be created. If the thread pool's prestartAllCoreThreads() method is called, the thread pool will create and start all basic threads.

2.workQueue (task queue): A blocking queue used to save tasks waiting to be executed. The following blocking queues can be selected.

  • ArrayBlockingQueue: It is a bounded blocking queue based on an array structure. This queue sorts the elements according to the FIFO (first in, first out) principle.
  • LinkedBlockingQueue: A blocking queue based on a linked list structure. This queue sorts elements by FIFO, and the throughput is usually higher than that of ArrayBlockingQueue. The static factory method Executors.newFixedThreadPool() uses this queue.
  • SynchronousQueue: A blocking queue that does not store elements. Each insert operation must wait until another thread calls the remove operation, otherwise the insert operation has been blocked, and the throughput is usually higher than LinkedBlockingQueue, which is used by the static factory Executors.newCachedThreadPool.
  • PriorityBlockingQueue: A wireless blocking queue with priority

maximumPoolSize (maximum number of thread pools): The maximum number of threads allowed to be created by the thread pool. If the queue is full and the number of threads already created is less than the maximum x number of threads, the thread pool will create a new thread to execute the task. It is worth noting that this parameter has no effect if the unbounded task queue is used.

ThreadFactory: Used to set the factory for creating threads. You can set a more meaningful name for each created thread through the thread factory.

RejectedExecutionHandler (saturation strategy): When the queue and thread pool are full, indicating that the thread pool is saturated, a strategy must be adopted to process the new tasks submitted. This policy defaults to AbortPolicy, which means that an exception will be thrown when a new task cannot be processed. The java thread pool provides the following four strategies:

  • AbortPolicy: throw exception directly
  • CallerRunsPolicy: The main thread executes this task
  • DiscardOldestPolicy: Discard the most recent task in the queue and execute the current task.
  • DiscardPolicy: Do not process, discard the call.

Of course, you can also implement the RejectedExecutionHandler interface custom strategy according to the needs of the application scenario. Tasks such as logging or persistent storage cannot handle.

KeepAliveTime (thread activity retention time): The time that the worker threads of the thread pool remain alive after they are idle. Therefore, if there are many tasks and the execution time of each task is very short, the time can be increased to improve the utilization of threads.

TimeUnit (the time unit for thread activity preservation): The available units are days (DAYS), hours (HOURS), minutes (MINUTES), milliseconds (MILLISECONDS), microseconds (MICROSECONDS), and nanoseconds (NANOSECONDS).

Submit a task to the thread pool

Tasks can be submitted to the thread pool using two methods, the execute() and submit() methods.

The execute() method is used to submit tasks that do not require a return value, so it is impossible to determine whether the task is successfully executed by the thread pool. The following code shows that the task input by the execute() method is an instance of the Runnable class

threadsPool.execute(new Runnable()){
      @Override
      public void run(){
          //todo
      }
}

The submit() method is used to submit tasks that require a return value. The thread pool will return an object of type future, which can be used to determine whether the task is successfully executed, and the return value can be obtained through the get() method of the future. The get() method will block the current thread until the task is completed, and use get The (long timeout, TimeUnit unit) method will block the current thread for a period of time and return immediately. At this time, it is possible that the task has not been executed.

Future<Object> feature = executor.submit(task);
               try{
                   Object s = feature.get();
               }catch(InterruptedException e){
                //todo 处理中断异常
               }finally{
                //关闭线程池
                executor.shutdown();
               }

close thread pool

A thread pool can be shut down by calling the thread pool's shutdown or shutdownNow methods. Their principle is to traverse the worker threads in the thread pool, and then call the interrupt method of the thread one by one to interrupt the thread, so the task that cannot respond to the interruption may never be terminated. But there is a certain difference between them, shutdownNow first sets the thread pool state to stop, then tries to stop all threads that are executing or suspending tasks, and returns the list of waiting tasks for execution, while shutdown Now just sets the thread pool state to shutdown state , and then interrupt all threads that are not executing tasks.

The isShutdown method returns true whenever either of the two shutdown methods is called. When all tasks have been closed, the thread pool is closed successfully, then calling the isTerminaed method will return true. As for which method to call to close the thread pool, it should be determined by the characteristics of the task submitted to the thread pool. Usually, the shutdown method is called to close the thread pool. If the task does not have to be executed, the shutdownNow method can be called.

Reasonably configure the thread pool

In order to configure the thread pool reasonably, you must first analyze the characteristics of the task, which can be analyzed from the following angles.

  • The nature of the tasks: CPU-intensive tasks, IO-intensive tasks, and mixed tasks.
  • Priority of tasks: high, medium and low
  • Task execution time: long, medium and short
  • Dependency of the task: Whether it depends on other system resources, such as database connections.

Tasks of different nature can be processed separately by thread pools of different sizes. CPU-intensive tasks should be configured with as few threads as possible, such as a thread pool with Ncpu+1 threads. Since the threads of IO-intensive tasks are not always executing tasks, you should configure as many threads as possible, such as 2*Ncpu. Mixed tasks, if they can be split into a CPU-intensive task and an IO-intensive task, as long as the execution time of the two tasks is too different, there is no need to split it.

Tasks with different priorities can be processed using the priority queue PriorityBlockingQueue, which allows tasks with higher priorities to be executed first.

thread pool monitoring

  • taskCount: The number of tasks that the thread pool needs to execute
  • completedTaskCount: The maximum number of threads ever created in the thread pool. Through this data, you can know whether the thread pool has ever been full. If the value is equal to the maximum size of the thread pool, it means that the thread pool has been full.
  • getPoolSize: The number of threads in the thread pool. If the thread pool is not destroyed, the threads in the thread pool will not be automatically destroyed, so the size will only increase or decrease.
  • getActiveCount: Get the number of active threads.

Monitoring by extending the thread pool. You can customize the thread pool by inheriting the thread pool, override the beforeExecute, afterExecute and terminated methods of the thread pool, or execute some code to monitor before the task is executed, after the execution and before the thread pool is closed.

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324037013&siteId=291194637