Java series - ThreadPool thread pool

Purpose: Master the use of the thread pool and various parameters in the API

什么是线程池: The thread pool is a thread usage mode. The thread pool maintains multiple threads, waiting for the supervisor to assign tasks that can be executed concurrently.

为什么使用线程池: In order to reduce the number of threads created and destroyed, so that each thread can be used multiple times, the number of threads can be adjusted according to the system conditions to prevent excessive memory consumption. In actual use, the server spends a lot of time and consumes system resources on creating and destroying threads, which can be optimized by using the thread pool.

To put it bluntly, don’t use the three basic methods of implements Runnable, extends Thread, and implements Callable to create threads. Use the thread pool class to manage thread initialization parameters, creation, and destruction.

ThreadPool core parameters

In the Java-JDK ThreadPool source code, the core parameters in the constructor are as follows

public ThreadPoolExecutor(
   /** 核心线程数 */
  int corePoolSize,
  /** 最大线程数 */
  int maximumPoolSize,
  /** 线程空闲时间 */
  long keepAliveTime,
  /** 时间单位 */
  TimeUnit unit,
  /** 任务队列 */
  BlockingQueue<Runnable> workQueue,
  /** 线程工厂 */
  ThreadFactory threadFactory,
  /** 拒绝策略 */
  RejectedExecutionHandler handler
)

When using the thread pool, focus on the three parameters of workQueue, threadFactory, and handler

The four thread pool creation methods that come with jdk

It is very simple to use JDK encapsulated classes to create thread pools, they are all ThreadPoolExecutorbased on a layer of abstract encapsulation

// 第一种线程池:固定个数的线程池,可以为每个CPU核绑定一定数量的线程数
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
// 缓存线程池,无上限
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 单一线程池,永远会维护存在一条线程
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
// 固定个数的线程池,可以执行延时任务,也可以执行带有返回值的任务。
ScheduledExecutorService scheduledThreadPool = ExecutorsnewScheduledThreadPool(5);

But we usually don't choose these four types provided by JDK, but use a custom way to create a thread pool, because it is more flexible, and you can set the number of pool parameters and rejection strategies at will

ExecutorService executorService = new ThreadPoolExecutor(0,
    10,
    1000,
    TimeUnit.SECONDS,
    new SynchronousQueue<>(),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy()
);

executorService.execute(task);

workQueue: task queue

In the construction method of the thread pool, the task queue is the fourth parameter.

Task queues are generally divided into: direct submission queues, bounded task queues, unbounded task queues, and priority task queues ;

  1. Submit directly to the queue

The direct submission queue is created using SynchronousQueue

SynchronousQueue is a special BlockingQueue . It has no capacity. It will block every time an insert operation is performed, and then it will be awakened after performing a delete operation. Otherwise, each delete operation must wait for the corresponding insert operation.

In the following example, SynchronousQueue is used to create a thread pool with a fixed number of threads with a core number of 0 and a maximum number of threads of 2. When executing a task, because of that 任务是耗时操作并且任务数量为3(循环了3次)大于2, it is directly submitted to the queue
. If the maximum number of thread pools is exceeded, the rejection policy will be directly executed RejectedExecutionHandler. The rejection strategy here AbortPolicymeans throwing an exception directly. The final print result is: Throw an exception + execute 2 tasks

public class FixThreadPool {
    
    

  // 1. 创建线程池(执行器)
  public static ExecutorService executorService = new ThreadPoolExecutor(
    0,
    2,
    60,
    TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>(),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy()
  );

  public static void main(String[] args) {
    
    
    // 2.任务
    Runnable task = new Runnable() {
    
    
      @Override
      public void run() {
    
    
          try {
    
    
              // 耗时操作,休眠3秒
              Thread.sleep(3000);
          } catch (InterruptedException e) {
    
    
              e.printStackTrace();
          }
          System.out.println("执行任务");
      }
    };

    for (int i = 0; i < 10; i++) {
    
    
      // 3.执行
      executorService.execute(task);
    }
  }
}

Results of the:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.ifdom.thread.creatThreadPool4Mode.FixThreadPool$1@3941a79c rejected from java.util.concurrent.ThreadPoolExecutor@506e1b77[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.ifdom.thread.creatThreadPool4Mode.FixThreadPool.main(FixThreadPool.java:42)
执行任务
执行任务
  1. bounded task queue
  1. If there are new tasks to be executed, and the number of created threads does not reach the maximum number of threads, then new threads will be created for execution. Until the number of new threads created exceeds the capacity set by the bounded task, then the rejection strategy will be ArrayBlockingQueueexecuted
  2. The upper limit of the number of threads is directly related to the state of the bounded task queue. If the initial capacity of the bounded queue is large or the state of overload is not reached, the number of threads will always be kept below corePoolSize. Otherwise, when the task queue is full, it will Use maximumPoolSize as the upper limit of the maximum number of threads.
public class WorkQueue4Mode {
    
    
    public static void main(String[] args) {
    
    

        // 2.有界执行队列
        ExecutorService executorService2 = new ThreadPoolExecutor(1,
                2,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        for (int i = 0; i < 10; i++) {
    
    
            int finalI = i;
            executorService2.execute(() -> {
    
    
                try {
    
    
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println("执行任务:" + finalI);
            });
        }
    }
}
  1. unbounded execution queue

LinkedBlockingQueuecreate using

An unbounded execution queue means that parameters maximumPoolSizeare invalid.

When using this mode, it should be noted that the cached queue tasks may cause system resource exhaustion due to too many

public class WorkQueue4Mode {
    
    
    public static void main(String[] args) {
    
    
        // 3.无界执行队列
        ExecutorService executorService3 = new ThreadPoolExecutor(1,
                2,
                300,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        // 执行任务  10W个任务
        for (int i = 0; i < 100000; i++) {
    
    
            int finalI = i;
            executorService3.execute(() -> {
    
    
                try {
    
    
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println("执行任务:" + finalI);
            });
        }
    }
}
  1. priority queue

In the other three queues, the tasks to be executed in the queue have no priority, so after the CPU is idle, the tasks in the threads to be executed are randomly selected for execution, and the priority queue allows us to specify the operating system to give priority to specific tasks

public class WorkQueue4Mode {
    
    
    public static void main(String[] args) {
    
    
        // 4.优先任务队列
        ExecutorService executorService4 = new ThreadPoolExecutor(1,
                2,
                1000,
                TimeUnit.SECONDS,
                new PriorityBlockingQueue<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        // 执行任务
        for (int i = 0; i < 10; i++) {
    
    
            executorService4.execute(new PriorityTaskHandle(i));
        }
    }
}
/**
 * 定义一个有优先级参数的任务
 * 示例:定义一个任务,通过传参传入优先级,如果当前任务优先级大于一,那么将将其优先级设定为传入参数
 **/
class PriorityTaskHandle implements Runnable, Comparable<PriorityTaskHandle> {
    
    
    private int priority;

    public PriorityTaskHandle() {
    
    
    }

    public PriorityTaskHandle(int priority) {
    
    
        this.priority = priority;
    }

    public int getPriority() {
    
    
        return priority;
    }

    public void setPriority(int priority) {
    
    
        this.priority = priority;
    }

    /**
    * 设定优先级
    * 数值越小,优先级越高
    **/
    @Override
    public int compareTo(PriorityTaskHandle o) {
    
    
        return this.priority > o.priority ? -1 : 1;
    }

    @Override
    public void run() {
    
    
        try {
    
    
            Thread.sleep(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("执行任务:" + this.priority);
    }
}

Example results: The task queue will cache all tasks when the thread pool is created for the first time, only in this way can the priority between them be compared

执行任务:0
执行任务:9
执行任务:8
执行任务:7
执行任务:6
执行任务:5
执行任务:4
执行任务:3
执行任务:2
执行任务:1

ThreadFactory: thread factory

Thread factories generally use the default: Executors.defaultThreadFactory()

RejectedExecutionHandler: rejection strategy

Generally, when we create a thread pool, in order to prevent resources from being exhausted, the task queue will choose to create a bounded task queue, but in this mode, if the task queue is full and the number of threads created by the thread pool reaches the maximum number of threads you set, Then you need to specify a reasonable rejection strategy. The rejection policy that comes with ThreadPoolExecutor is as follows:

  • AbortPolicy: This strategy will directly throw an exception, preventing the system from working properly. If it is a relatively critical business, it is recommended to use this rejection strategy, so that when the system cannot bear a larger amount of concurrency, it can be detected in time through exceptions

  • DiscardPolicy: Discard the task, but do not throw an exception. If the thread queue is full, subsequent submitted tasks will be discarded, and silently discarded. In the business scenario, it is necessary to allow the loss of tasks, so use this strategy;

  • DiscardOldestPolicy: Discard the oldest task in the queue, that is, the task that is added first in the current task queue and is about to be executed; then try to submit the rejected task again

  • CallerRunsPolicy: The task is processed by the calling thread (the thread that submitted the task). When I was working on an SMS platform, the thread pool adopted this strategy, because none of our SMS messages can be missed, and each task must be processed.

RejectedExecutionHandler: The execution order of the thread pool

  • Create threads when the number of threads is less than the number of core threads.
  • When the number of threads is greater than or equal to the number of core threads and the task queue is not full, put the task into the task queue.
  • When the number of threads is greater than or equal to the number of core threads and the task queue is full, if the number of threads is less than the maximum number of threads, create a thread.
  • If the number of threads is equal to the maximum number of threads, the rejection policy is executed

Several states of the thread pool

In the source code, you will see these four strange values, they represent the state of the thread pool

// runState is stored in the high-order bits
//1110 0000 0000 0000 0000 0000 0000 0000
private static final int RUNNING    = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP       =  1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING    =  2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED =  3 << COUNT_BITS;
status value describe
RUNNING Running state, able to accept new tasks and process tasks in the blocking queue
SHUTDOWN Closed state, does not accept new tasks, but will process tasks in the blocking queue, and execute shutdown() of the thread pool
STOP In the stopped state, new tasks will not be accepted, and tasks in the waiting queue will not be processed and tasks in progress will be interrupted. The method shutDownNow() of the thread pool is called
TIDYING Clean up the state, that is, all tasks are stopped, the number of threads in the thread pool is equal to 0, and the hook function terminated() will be executed. If the user wants to perform corresponding processing when the thread pool becomes TIDYING; it can be realized by overloading the terminated() function
TERMINATED The end state, the terminated() method is executed

Expansion: ThreadPoolTaskExecutorwhat ThreadPoolExecutoris the difference with ?

  • ThreadPoolTaskExecutor is in the spring core package, and ThreadPoolExecutor is JUC in JDK.
  • ThreadPoolTaskExecutor encapsulates ThreadPoolExecutor.

Expansion: Priority Queue PriorityQueue source code

PriorityQueue internally creates new ReentrantLock() reentrant locks to control lock competition for executing queue tasks.

  1. Scaling: Attempt to scale up to the point where at least one more element can be added (but typically expands by about 50%), giving up (allowing retries) in contention (we expect this to happen rarely). Can only expand while holding a lock
  private void tryGrow(Object[] array, int oldCap) {
    
    
      lock.unlock(); // 必须先释放锁,稍后再持有锁
      Object[] newArray = null;
      if (allocationSpinLock == 0 &&
          UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                    0, 1)) {
    
    
          try {
    
    
              int newCap = oldCap + ((oldCap < 64) ?
                                      (oldCap + 2) : // grow faster if small
                                      (oldCap >> 1));
              if (newCap - MAX_ARRAY_SIZE > 0) {
    
        // possible overflow
                  int minCap = oldCap + 1;
                  if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                      throw new OutOfMemoryError();
                  newCap = MAX_ARRAY_SIZE;
              }
              if (newCap > oldCap && queue == array)
                  newArray = new Object[newCap];
          } finally {
    
    
              allocationSpinLock = 0;
          }
      }
      if (newArray == null) // back off if another thread is allocating
          Thread.yield();
      lock.lock(); // 再次持有锁
      if (newArray != null && queue == array) {
    
    
          queue = newArray;
          System.arraycopy(array, 0, newArray, 0, oldCap);
      }
  }
  1. Compare
    /**
    * 二分法查找
    * k: 队列容量
    * x: 目标元素
    * array:  queue 队列
    * cmp: 比较器
    *
    * >>> : 右移一位,无符号位,高位填0
    * >> : 右移一位,有符号位,高位补符号位
    **/
private static <T> void siftUpUsingComparator(int k, T x, Object[] array,
                                    Comparator<? super T> cmp) {
    
    
    while (k > 0) {
    
    
        int parent = (k - 1) >>> 1;
        Object e = array[parent];
        if (cmp.compare(x, (T) e) >= 0)
            break;
        array[k] = e;
        k = parent;
    }
    array[k] = x;
}

Extension: encapsulating bit tool classes in Spring

Package:

package com.springconfig.config;

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.*;

/**
 * @Author [email protected]
 **/
public class ThreadPoolConfig {
    
    
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    
    
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int i = Runtime.getRuntime().availableProcessors();
        //核心线程数目
        executor.setCorePoolSize(i * 2);
        //指定最大线程数
        executor.setMaxPoolSize(i * 2);
        //队列中最大的数目
        executor.setQueueCapacity(i * 2 * 10);
        //线程名称前缀
        executor.setThreadNamePrefix("ThreadPoolTaskExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //当调度器shutdown被调用时等待当前被调度的任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //线程空闲后的最大存活时间
        executor.setKeepAliveSeconds(60);
        //加载
        executor.initialize();
        System.out.println("初始化线程池成功");
        return executor;
    }

    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
    
    
        //获取cpu核心数
        int i = Runtime.getRuntime().availableProcessors();
        //核心线程数
        int corePoolSize = i * 2;
        //最大线程数
        int maximumPoolSize = i * 2;
        //线程无引用存活时间
        long keepAliveTime = 60;

        TimeUnit unit = TimeUnit.SECONDS;

        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(i * 2 * 10);

        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        //拒绝执行处理器
        RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
        //创建线程池
        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }
}

use:

@RestController
public class BookController {
    
    

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @GetMapping
    public void getCity() {
    
    
        threadPoolTaskExecutor.execute(()->{
    
    
            System.out.println("执行任务");
        });
    }
}

Guess you like

Origin blog.csdn.net/win7583362/article/details/127166798