[High Concurrency] Analysis of the thread pool and ThreadPoolExecutor class that I have to say

Hello everyone, I'm Glacier~~

Today, let's briefly talk about the ThreadPoolExecutor class in the thread pool. Okay, not much more to say, let's start today's topic.

1. Throwing bricks to attract jade

Since Java supports the execution of corresponding tasks in a multi-threaded manner, why is thread pool technology provided in JDK1.5? We all make up our own minds on this question, and use more brains, there is definitely no harm, hahaha. . .

Speaking of thread pool technology in Java, it is involved in many frameworks and asynchronous processing middleware, and its performance has withstood a long test. It can be said that Java's thread pool technology is one of the core technologies of Java. In the field of Java's high concurrency, Java's thread pool technology is a topic that can never be avoided. Since Java's thread pool technology is so important (how can it be said that it is so important? That is quite important, that guy is very important, hahaha), then, in this article, we will briefly talk about the thread pool and ThreadPoolExecutor class. As for the technical details in the thread pool and the underlying principles and source code analysis of ThreadPoolExecutor, we will conduct in-depth analysis in the [High Concurrency Special] column.

Introduction: This article is the beginning of the thread pool in high concurrency. I will not explain it in depth for the time being, but let you understand one of the core classes in the thread pool as a whole - ThreadPoolExecutor, about the underlying principle and source code implementation of ThreadPoolExecutor , as well as the underlying principles and source code implementation of other technical details in the thread pool, we will be dead in the next article of [High Concurrency Topic].

Second, the disadvantages of Thread directly creating threads

(1) Each time a new Thread creates a new object, the performance is poor. (2) There is a lack of unified management of threads, and there may be unlimited new threads, competing with each other, which may occupy too many system resources and cause a crash or OOM. (3) Lack of more functions, such as more execution, regular execution, thread interruption. (4) For other drawbacks, everyone can make up their own brains and use their brains more. There is no harm, haha.

3. The benefits of thread pools

(1) Reuse existing threads, reduce the overhead of object creation and death, and have good performance. (2) It can effectively control the maximum number of concurrent threads, improve system resource utilization, and avoid excessive resource competition and blockage. (3) Provide functions such as timed execution, periodic execution, single thread, and concurrent number control. (4) Provide a method to support thread pool monitoring, which can monitor the resources of the thread pool in real time. (5) Other benefits, everyone can make up their own brains, use more brains, there is no harm, haha.

Fourth, the thread pool

1. Thread pool class structure relationship

The structural relationship of some interfaces and classes in the thread pool is shown in the following figure.

The underlying principles and source code of these interfaces and classes will be discussed later.

2. Create a commonly used class for thread pools - Executors

  • Executors.newCachedThreadPool: Create a cacheable thread pool. If the size of the thread pool exceeds the need, it can flexibly recycle idle threads. If there are no recyclable threads, create a new thread
  • Executors.newFixedThreadPool: Create a fixed-length thread pool, which can control the maximum number of concurrent threads, and the excess threads will wait in the queue
  • Executors.newScheduledThreadPool: Create a fixed-length thread pool to support scheduled and periodic task execution
  • Executors.newSingleThreadExecutor: Create a single-threaded thread pool, use a unique worker thread to execute tasks, and ensure that all tasks are executed in the specified order (first-in, first-out or priority)
  • Executors.newSingleThreadScheduledExecutor: Create a single-threaded thread pool that supports scheduled and periodic task execution
  • Executors.newWorkStealingPool: Create a work-stealing thread pool with parallelism level

3. Several states of the thread pool instance

  • Running: Running state, can receive newly submitted tasks, and can also process tasks in the blocking queue
  • Shutdown: Closed state, can no longer receive newly submitted tasks, but can process tasks that have been saved in the blocking queue. When the thread pool is in the Running state, calling the shutdown() method will cause the thread pool to enter this state
  • Stop: 不能接收新任务,也不能处理阻塞队列中已经保存的任务,会中断正在处理任务的线程,如果线程池处于Running或Shutdown状态,调用shutdownNow()方法,会使线程池进入该状态
  • Tidying: 如果所有的任务都已经终止,有效线程数为0(阻塞队列为空,线程池中的工作线程数量为0),线程池就会进入该状态。
  • Terminated: 处于Tidying状态的线程池调用terminated()方法,会使用线程池进入该状态

注意:不需要对线程池的状态做特殊的处理,线程池的状态是线程池内部根据方法自行定义和处理的。

4.合理配置线程的一些建议

(1)CPU密集型任务,就需要尽量压榨CPU,参考值可以设置为NCPU+1(CPU的数量加1)。 (2)IO密集型任务,参考值可以设置为2*NCPU(CPU数量乘以2)

五、线程池最核心的类之一——ThreadPoolExecutor

1.构造方法

ThreadPoolExecutor参数最多的构造方法如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler rejectHandler) 
复制代码

其他的构造方法都是调用的这个构造方法来实例化对象,可以说,我们直接分析这个方法之后,其他的构造方法我们也明白是怎么回事了!接下来,就对此构造方法进行详细的分析。

注意:为了更加深入的分析ThreadPoolExecutor类的构造方法,会适当调整参数的顺序进行解析,以便于大家更能深入的理解ThreadPoolExecutor构造方法中每个参数的作用。

上述构造方法接收如下参数进行初始化:

(1)corePoolSize:核心线程数量。

(2)maximumPoolSize:最大线程数。

(3)workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。

其中,上述三个参数的关系如下所示:

  • 如果运行的线程数小于corePoolSize,直接创建新线程处理任务,即使线程池中的其他线程是空闲的。
  • 如果运行的线程数大于等于corePoolSize,并且小于maximumPoolSize,此时,只有当workQueue满时,才会创建新的线程处理任务。
  • 如果设置的corePoolSize与maximumPoolSize相同,那么创建的线程池大小是固定的,此时,如果有新任务提交,并且workQueue没有满时,就把请求放入到workQueue中,等待空闲的线程,从workQueue中取出任务进行处理。
  • 如果运行的线程数量大于maximumPoolSize,同时,workQueue已经满了,会通过拒绝策略参数rejectHandler来指定处理策略。

根据上述三个参数的配置,线程池会对任务进行如下处理方式:

当提交一个新的任务到线程池时,线程池会根据当前线程池中正在运行的线程数量来决定该任务的处理方式。处理方式总共有三种:直接切换、使用无限队列、使用有界队列。

  • 直接切换常用的队列就是SynchronousQueue。
  • 使用无限队列就是使用基于链表的队列,比如:LinkedBlockingQueue,如果使用这种方式,线程池中创建的最大线程数就是corePoolSize,此时maximumPoolSize不会起作用。当线程池中所有的核心线程都是运行状态时,提交新任务,就会放入等待队列中。
  • 使用有界队列使用的是ArrayBlockingQueue,使用这种方式可以将线程池的最大线程数量限制为maximumPoolSize,可以降低资源的消耗。但是,这种方式使得线程池对线程的调度更困难,因为线程池和队列的容量都是有限的了。

根据上面三个参数,我们可以简单得出如何降低系统资源消耗的一些措施:

  • 如果想降低系统资源的消耗,包括CPU使用率,操作系统资源的消耗,上下文环境切换的开销等,可以设置一个较大的队列容量和较小的线程池容量。这样,会降低线程处理任务的吞吐量。
  • 如果提交的任务经常发生阻塞,可以考虑调用设置最大线程数的方法,重新设置线程池最大线程数。如果队列的容量设置的较小,通常需要将线程池的容量设置的大一些,这样,CPU的使用率会高些。如果线程池的容量设置的过大,并发量就会增加,则需要考虑线程调度的问题,反而可能会降低处理任务的吞吐量。

接下来,我们继续看ThreadPoolExecutor的构造方法的参数。

(4)keepAliveTime:线程没有任务执行时最多保持多久时间终止 当线程池中的线程数量大于corePoolSize时,如果此时没有新的任务提交,核心线程外的线程不会立即销毁,需要等待,直到等待的时间超过了keepAliveTime就会终止。

(5)unit:keepAliveTime的时间单位

(6)threadFactory:线程工厂,用来创建线程 默认会提供一个默认的工厂来创建线程,当使用默认的工厂来创建线程时,会使新创建的线程具有相同的优先级,并且是非守护的线程,同时也设置了线程的名称

(7)rejectHandler:拒绝处理任务时的策略

如果workQueue阻塞队列满了,并且没有空闲的线程池,此时,继续提交任务,需要采取一种策略来处理这个任务。

线程池总共提供了四种策略:

  • 直接抛出异常,这也是默认的策略。实现类为AbortPolicy。
  • 用调用者所在的线程来执行任务。实现类为CallerRunsPolicy。
  • 丢弃队列中最靠前的任务并执行当前任务。实现类为DiscardOldestPolicy。
  • 直接丢弃当前任务。实现类为DiscardPolicy。

2.ThreadPoolExecutor提供的启动和停止任务的方法

(1)execute():提交任务,交给线程池执行 (2)submit():提交任务,能够返回执行结果 execute+Future (3)shutdown():关闭线程池,等待任务都执行完 (4)shutdownNow():立即关闭线程池,不等待任务执行完

3.ThreadPoolExecutor提供的适用于监控的方法

(1)getTaskCount():线程池已执行和未执行的任务总数 (2)getCompletedTaskCount():已完成的任务数量 (3)getPoolSize():线程池当前的线程数量 (4)getCorePoolSize():线程池核心线程数 (5)getActiveCount():当前线程池中正在执行任务的线程数量

好了,今天就到这儿吧,我是冰河,我们下期见~~

Guess you like

Origin juejin.im/post/7088972409876774942