自定义线程池

参数含义

image.png

  1. corePoolSize:the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set
    (核心线程数大小:不管它们创建以后是不是空闲的。线程池需要保持 corePoolSize 数量的线程,除非设置了 allowCoreThreadTimeOut。)
  2. maximumPoolSize:the maximum number of threads to allow in the pool。
    (最大线程数:线程池中最多允许创建 maximumPoolSize 个线程。)
  3. keepAliveTime:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating。
    (存活时间:如果经过 keepAliveTime 时间后,超过核心线程数的线程还没有接受到新的任务,那就回收。)
  4. unit:the time unit for the {@code keepAliveTime} argument
    (keepAliveTime 的时间单位。)
  5. workQueue:the queue to use for holding tasks before they are executed. This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method。
    (存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在这里。它仅仅用来存放被 execute 方法提交的 Runnable 任务。所以这里就不要翻译为工作队列了,好吗?不要自己给自己挖坑。)
  6. threadFactory:the factory to use when the executor creates a new thread。
    (线程工厂:用来创建线程工厂。比如这里面可以自定义线程名称,当进行虚拟机栈分析时,看着名字就知道这个线程是哪里来的,不会懵逼。)
  7. handler :the handler to use when execution is blocked because the thread bounds and queue capacities are reached。
    (拒绝策略:当队列里面放满了任务、最大线程数的线程都在工作时,这时继续提交的任务线程池就处理不了,应该执行怎么样的拒绝策略。)

线程池执行策略

image.png

阻塞队列可选队列

image.png

线程池的核心线程数设置

如果是 CPU 密集型的,可以把核心线程数设置为核心数+1。
为什么要加一呢?
《Java并发编程实战》一书中给出的原因是:即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费。

如果是IO密集型的
image.png

image.png

重点:
动态的配置线程池

如何设置线程的数量

Cpu 密集型

对于 CPU 密集型计算,多线程本质上是提升多核 CPU 的利用率,所以对于一个 4 核的 CPU,每个核一个线程,理论上创建 4 个线程就可以了,再多创建线程也只是增加线程切换的成本。所以,对于 CPU 密集型的计算场景,理论上“线程的数量 =CPU 核数”就是最合适的。不过在工程上,线程的数量一般会设置为“CPU 核数 +1”,这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,从而保证 CPU 的利用率。

Io 密集型

对于 I/O 密集型的计算场景,比如前面我们的例子中,如果 CPU 计算和 I/O 操作的耗时是 1:1,那么 2 个线程是最合适的。如果 CPU 计算和 I/O 操作的耗时是 1:2,那多少个线程合适呢?是 3 个线程,如下图所示:CPU 在 A、B、C 三个线程之间切换,对于线程 A,当 CPU 从 B、C 切换回来时,线程 A 正好执行完 I/O 操作。这样 CPU 和 I/O 设备的利用率都达到了 100%。
image.png

通过上面这个例子,我们会发现,对于 I/O 密集型计算场景,最佳的线程数是与程序中 CPU 计算和 I/O 操作的耗时比相关的,我们可以总结出这样一个公式:

  1. 最佳线程数 =1 +(I/O 耗时 / CPU 耗时)

我们令 R=I/O 耗时 / CPU 耗时,综合上图,可以这样理解:当线程 A 执行 IO 操作时,另外 R 个线程正好执行完各自的 CPU 计算。这样 CPU 的利用率就达到了 100%。不过上面这个公式是针对单核 CPU 的,至于多核 CPU,也很简单,
只需要等比扩大就可以了,计算公式如下:

  1. 最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]

总结

其实我们很多时候通过公式得到的结果是很不靠谱的,因为一台机器上的应用干扰因数很多,所以我们更多的时候是根据公式或者经得到一个零时值,后续在通过压测等数据,评估出合适的线程数量。

改变线程池任务的优先级

很简单的一个小技巧,就是创建类时,给类增加一个有参的构造方法,并且在创建任务的时候将线程的优先级以参数的存入RUN方法中,以此来实现任务优先级的改变

线程池 - 图7