JAVA并发编程系列(11)线程池底层原理架构剖析

面试官:说说JAVA线程池的几个核心参数?

之前我们用了10篇文章详细剖析了synchronized、volatile、CAS、AQS、ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier、并发锁、Condition等各个核心基础原理,今天开始我们说说并发领域的各种工具包还有应用场景。

1、为什么要用线程池?

日常频繁创建和销毁线程是非常消耗系统资源的操作,会降低系统的整体性能。而线程池通过池化的管理思想,把线程的创建、任务调度、任务执行、任务等待、任务拒绝、销毁等全生命周期各个环节都进行统一管理,大幅提升系统性能,提高线程利用率以及任务响应。

2、线程池核心参数意义

一共有6个:

arduino 复制代码
    private volatile ThreadFactory threadFactory;
  
    private volatile RejectedExecutionHandler handler;
    
    private volatile long keepAliveTime;//非核心线程空闲超时时间

    private volatile boolean allowCoreThreadTimeOut;

    private volatile int corePoolSize;//核心线程数量

    private volatile int maximumPoolSize;//最大线程数量
2.1 corePoolSize 核心线程数量

核心线程即使空闲,也不会被回收。如果当前线程数量小于corePoolSize,即使其他核心线程空闲,线程池也会新增创建一个线程来执行任务。

核心线程会一直存活在线程池里,但是如何设置了线程池的allowCoreThreadTimeOut为true,则核心线程空闲一定时间也会被回收。

arduino 复制代码
private volatile boolean allowCoreThreadTimeOut
2.2 maximumPoolSize最大线程数量

线程池最大线程数量maximumPoolSize = 核心线程数量corePoolSize + 非核心线程数量。

场景A:当线程数量>=核心线程数量,且等待队列未满,将任务加入到等待队列。

场景B:当队列已满,并且线程数量<maximumPoolSize ,就新增非核心线程。这种非核心线程就是在空闲时间大于keepAliveTime,就会被回收。

场景C:当队列已满,且线程数量大于等于maximumPoolSize,就触发拒绝策略handler拒绝任务。

2.3 keepAliveTime 线程空闲时间

keepAliveTime允许线程的最大的空闲存活时间。如果一个非核心线程在空闲状态下持续超过keepAliveTime了,就会被回收,以及线程池设置allowCoreThreadTimeOut为true,核心线程也会被回收。

2.4 workQueue任务存储队列

用于存储等待执行的任务。主要有三种类型。

第一个,直接提交SynchronousQueue,把任务直接提交给工作线程而不放到等待队列。这个队列不存储元素,newCachedThreadPool使用的就是这种同步移交队列,吞吐量比LinkedBlockingQueue大。

第二个,LinkedBlockingQueue无界队列,队列的最多容量为int的最大值,相当于无限容量。newFixedThreadPool和newSingleThreadPool使用的就是LinkedBlockingQueue,这个吞吐量比ArrayBlockingQueue高。

第三个,ArrayBlockingQueue有界队列,可以有效避免资源耗尽。

除了这三种,还有DelayQueue、PriorityBlockingQueue。

2.5 threadFactory线程工厂

线程工厂,用来创建线程。

2.6 handler拒绝策略

拒绝任务的策略。当线程数量达到最大线程数量maximumPoolSize,以及等待队列workQueue也满了,这时候需要用来拒绝任务提交时的策略handler。策略类型有抛异常、用调用者所在的线程执行任务、丢弃队列中第一个任务执行当前任务、直接丢弃任务)。

策略种类有:

AbortPolicy:直接抛出异常。

DiscardPolicy:丢弃任务,不抛异常。

DiscardOldestPolicy:将等待队列里等待最久的任务丢弃。

CallerRunsPolicy: 哪个线程提交的任务,哪个线程去处理。

4、有几种线程池?

JAVA线程池ThreadPoolExcutor,常见的有这四种线程池。

scss 复制代码
//初始化一个无限线程的线程池,无需等待队列
Executors.newCachedThreadPool()
//初始化一个支持周期性运行的线程池
Executors.newScheduledThreadPool(10);
//初始化固定数目线程的线程池
Executors.newFixedThreadPool(10);
//初始化仅有一个线程的线程池
Executors.newSingleThreadExecutor();
4.1 Executors.newCachedThreadPool()可缓存线程池

创建一个可缓存的线程池,如果线程数量大于处理任务时,空闲线程被回收;当提交任务增加时,又可以新建线程去处理任务。这种线程池的线程数无限制,corePoolSize数值为0, maximumPoolSize 的数值都是为 Integer.MAX_VALUE。

线程可复用性很高,可以减少频繁创建/销毁线程,减少系统开销。工作队列workQueue选用SynchronousQueue。

4.2 Executors.newScheduledThreadPool()定时调度线程池

也是固定长度的线程池,但是支持以延迟或者定时的方式去执行任务。

4.3 newSingleThreadExecutor()单线程的线程池

一个线程,corePoolSize 和 maximumPoolSize 的数值都是为 1,线程池里只有一个工作线程执行任务。若这个唯一的线程异常出问题了,会新建另一个线程来替代。所有任务在等待队列是FIFO顺序被执行。

4.4 Executors.newFixedThreadPool()固定长度线程池

每次提交任务的时候就会创建一个新的线程,直到达到线程池的最大数量限制,如何任务大于线程数量,就进入等待队列。 corePoolSize 和 maximumPoolSize 的数值相等。工作队列选用LinkedBlockingQueue。

最后,我们上一个线程池demo,固定线程池,每次最多N个线程在执行任务,其他任务等待。

java 复制代码
package lading.java.mutithread;

import cn.hutool.core.date.DateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 固定线程池,线程数量为3
 * 提交多个count+1任务计算
 *
 */
public class Demo013ThreadPool {
    //计算任务count
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {
        //固定数量线程池
        ExecutorService service = Executors.newFixedThreadPool(3);
        Executors.newScheduledThreadPool(10);
        //提交20次,对count+1的任务
        for (int i = 1; i < 20 + 1; i++) {
            service.submit(new Thread(() -> {
                //模拟每次计算耗时2s
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //完成本次对count+1计算任务
                System.out.println(DateTime.now().toString("YYYY-MM-dd hh:mm:ss") + " " +Thread.currentThread().getName() + "线程 执行计算任务:" + count.incrementAndGet());
            }));
        }
        service.shutdown();
    }
}

每次只有三个线程在运行任务,其他任务等之前任务执行完成后再执行。

今天就分享到这,明天分享并发容器CurrentHashMap。

相关推荐
尚学教辅学习资料8 分钟前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
雷神乐乐24 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
小刘|28 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
逊嘘1 小时前
【Java语言】抽象类与接口
java·开发语言·jvm
morris1311 小时前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员1 小时前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU1 小时前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie61 小时前
在IDEA中使用Git
java·git