首页 > 编程语言 >Java-线程-线程池

Java-线程-线程池

时间:2024-05-09 16:11:20浏览次数:23  
标签:Java 队列 创建 任务 线程 核心 执行

0.背景

参考资料:Java线程池实现原理及其在美团业务中的实践

在 Java 早期,每次创建线程时,都要涉及到线程的创建、销毁以及资源管理,这对于系统的性能和资源利用率是一种浪费。

因此,Java 提供了线程池的概念,以提高线程的管理效率和性能。

  • 资源管理优化:传统的线程创建和销毁需要涉及系统资源的分配和释放,而频繁的线程创建和销毁会导致资源的浪费和性能下降。

    线程池通过维护一组可重用的线程来减少线程的创建和销毁次数,从而优化了系统资源的利用。

  • 线程复用:线程池可以维护一定数量的线程池,这些线程可以被重复利用来执行多个任务,而不必每次都创建新线程。

    这样可以避免频繁地创建和销毁线程,提高了系统的性能和效率。

  • 任务调度和管理:线程池可以对任务进行调度和管理,可以根据任务的优先级和类型来调度执行线程,提供了更加灵活和可控的任务执行方式。

  • 并发控制:线程池提供了一种方便的方式来控制并发执行的线程数量,可以限制同时执行的线程数量,从而避免因线程过多导致系统资源耗尽或性能下降的问题。

1.概念

1.1 关键词

记忆方法:核大存单队略厂(这里和大存单的队伍略长)

1.1.1 核心线程数 corePoolSize

  • 含义:指定线程池中保持的活动线程数。除非设置了 allowCoreThreadTimeOuttrue,否则这些线程不会被回收。
  • 作用:核心线程数决定了线程池中保持的最小活动线程数,即使线程处于空闲状态也不会被回收。

1.1.2 最大线程数 maximumPoolSize

  • 含义:指定线程池中允许创建的最大线程数。当任务队列已满且核心线程数已达到最大时,线程池会创建新的线程来执行任务,直到达到最大线程数为止。
  • 作用:最大线程数决定了线程池中允许创建的最大线程数,用于处理任务队列中的任务。

1.1.3 空闲线程存活时间 keepAliveTime

  • 含义:指定空闲线程的存活时间,即当线程池中的线程处于空闲状态且空闲时间超过该值时,这些空闲线程可以被回收。
  • 作用:通过设置线程的存活时间,可以控制线程池中线程的数量,避免因线程过多导致系统资源的浪费。

1.1.4 空闲线程存活时间单位 unit

  • 含义:指定空闲线程存活时间的单位,可以是纳秒、微秒、毫秒、秒、分钟、小时或天等。
  • 作用:单位参数用于指定空闲线程存活时间的时间单位,与 keepAliveTime 参数配合使用。

1.1.5 工作队列 workQueue

  • 含义:任务队列,用于保存等待执行的任务。线程池中的线程会从任务队列中取出任务执行。
  • 作用:工作队列用于存储提交给线程池但尚未执行的任务,当线程池中的线程处于忙碌状态时,新的任务会被放入任务队列等待执行。

1.1.6 拒绝策略 rejectedExecutionHandler

  • 含义:拒绝策略,用于处理当任务无法被线程池执行时的情况。当任务队列已满且无法接受新的任务时,或者线程池已关闭但仍然有任务提交时,会触发拒绝策略。
  • 作用:拒绝策略定义了线程池无法处理任务时的行为,包括抛出异常、丢弃任务、阻塞调用者或者执行任务的线程等待一段时间后再尝试执行任务等。选择合适的拒绝策略能够有效地处理任务提交过载的情况,保护线程池和系统的稳定性。

1.1.7 线程工厂 threadFactory

  • 含义:线程工厂,用于创建新的线程。当线程池需要创建新线程时,会调用线程工厂的 newThread(Runnable r) 方法来创建线程。
  • 作用:线程工厂用于创建线程池中的线程实例,通过自定义线程工厂,可以控制线程的创建过程,如设置线程的名称、优先级、是否为守护线程等。

1.2 线程池的状态

线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。

运行状态 是否接收任务 是否处理任务 状态描述
RUNNING 能接受新提交的任务,并且也能处理阻塞队列中的任务。
SHUTDOWN × 不再接受新提交的任务,可以继续处理阻塞队列中已保存的任务。
STOP × × 不再接受新任务,也不继续处理队列中的任务,会中断正在处理任务的线程。
TIDYING - - 所有的任务都已终止了,workerCount(有效线程数)为0。
TERMINATED - - 终止状态,在 terminated() 方法(预留)执行完后进入该状态。

状态转换如下:

图3 线程池生命周期

1.3 运行过程

有核心线程,优先用核心线程。核心线程不够用,先往工作队列里放。工作队列都放不下了,再创建非核心线程来执行。实在是不行了,只能进行拒绝。

  • 开始,接收到提交的一个任务。

  • 判断线程池是否还在运行

    • 如果不是RUNNING状态,则不再接受任务提交,进行拒绝策略。

    • 如果是RUNNINNG状态,可以接收任务提交。

      判断当前工作线程数与核心线程数的关系

      • 小于核心线程数,新建工作线程(核心)并执行。

      • 大于等于核心线程数

        检查工作队列情况

        • 工作队列未满,添加任务到工作队列中,等待获取执行。

        • 工作队列已满

          检查最大线程数

          • 未达到最大线程数,新建工作线程(非核心)执行。
          • 已经达到最大线程数,进行任务拒绝。

图4 任务调度流程

1.4 理解

注意,以下是我的理解,我半桶水,你记得带自己的思考来阅读喔。

1.4.1 核心线程数和最大线程数

忽略核心线程是否可以销毁

核心线程必定在运行,是我们使用的最小成本。而最大线程是一个兜底机制,不可能无休止的来创建线程,必定要有个度。

线程池中的任务,是依赖核心线程和非核心线程来执行的,不是说跟线程创建销毁跟工作队列没关系,而是侧重点不同,工作队列的目的是缓冲。

1.4.2 工作队列存在的意义

工作队列的存在,是为了提供一个缓冲,帮助线程池保持一个最小可运转的线程数量,避免频繁的创建和销毁线程。

好,我们回顾上面的图,核心线程没满的时候,我们可以新建线程来执行,在核心线程内的数量,我们是很好接受的。

那么,当我的核心线程都不够用了,这个时候,暴力的方式当然是直接新建线程来执行。

  • 优点,核心线程在忙碌,我直接搂一个新线程,立马就能执行,效率很高。
  • 缺点,涉及到的频繁的创建线程(高成本)。

有了工作队列后,我们核心线程都在忙碌时,选择先将任务放到队列里等一等,实在再放不下了,再来创建线程执行。

我们还可以极端点,假设我们新建一个线程要2s,执行一个任务只要0.5s。

核心线程已满,这个时候,我们是不是等个0.5s就能空闲出一个核心线程,然后就有得执行了。而不是选择立马新建一个,耗费2s。

1.4.3 ThreadPoolExecutor分析

1.4.3.1 关于参数校验

经常会有面试问,核心线程数能设置为0吗?

注意这是构造方法,是做了校验的。

A.参数不合法异常 IllegalArgumentException
  • 核心线程数 < 0
  • 最大线程数 <= 0
  • 最大线程数 < 核心线程数
  • 线程存活时间小于0
B.空指针异常NullPointerException
  • 工作队列为null
  • 线程工厂为null
  • 拒绝策略为null
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这个构造函数,最少需要5个核心参数喔。

image-20240509154827473

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
1.4.3.2 核心线程数可以为0吗?

可以

线程池也可以接收任务,线程池会依赖非核心线程来执行任务。

任务首先会被放入工作队列进行缓冲,如果工作队列已满,再根据最大线程数的限制来新建非核心线程来执行任务。

只有在工作队列已满,且线程池中的线程数未达到最大线程数时,才会创建新的线程,如果线程池已达到最大线程数,则不会再创建新线程,根据拒绝策略来处理任务。

然后呢,这个时候也有个问题,由于我们没有核心线程,直接被放到工作队列里,这个队列没满的话,就不会创建线程,相当于在干等。

嗯,就跟我们写的代码一样,让人感到无语。

1.4.3.3 最大线程数可以为0吗?

不可以

首先通过前面可以知道,最大线程数必须比核心线程数大,然后呢,核心线程数不能小于0(参数异常),但是核心线程数可以等于0。

然后呢,还有个校验,是最大线程数是必须大于核心线程数的,所以说呀,这个最大线程数,最小值是1

哦,sorry,最大线程数的判断直接是maximumPoolSize <= 0 抛异常。

我没骗你,你自己看图。

image-20240509155047977

image-20240509155030262

1.4.4 执行流程代码简述

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);
    }
    else if (!addWorker(command, false))
        reject(command);
}

2.实现

2.1 Executor接口

  • 定义了一个单一的方法 execute(Runnable command),用于执行提交的任务。
  • 只关注任务的提交方式,不关心任务的执行细节。

image-20240507223239290

2.2 ExecutorService接口

  • Executor 接口的子接口,提供了更多的方法用于任务管理和线程池控制。
  • 定义了一系列方法来提交任务、关闭线程池、管理任务的执行状态等。

image-20240507223408199

2.3 Executors类

  • 是一个工具类,提供了一系列静态方法用于创建不同类型的线程池。
  • 通过 Executors 类可以快速方便地创建各种预定义类型的线程池,如固定大小线程池、可缓存线程池等

image-20240507223316563

2.4 ThreadPoolExecutor类

  • ExecutorService 接口的一个具体实现类,也是 Executors 类创建线程池的底层实现。
  • 提供了一个灵活的线程池实现,可以通过构造方法来设置各种参数,如核心线程数、最大线程数、任务队列、拒绝策略等。

image-20240507223451061

3.进阶

3.1 线程池中的异常

3.2 自定义拒绝策略

3.3 获取任务执行结果

3.3.1 Future

3.3.2 Callable

3.4 定时任务和周期性任务的调度和执行

3.4.1 ScheduledExecutorService

3.4.2 ScheduledThreadPoolExecutor

标签:Java,队列,创建,任务,线程,核心,执行
From: https://www.cnblogs.com/yang37/p/18182528

相关文章

  • Java学设计模式之装饰器模式
    一、模式概念1.1什么是模式装饰模式是一种结构型设计模式,它允许向现有对象动态添加新功能,同时又不改变其结构。装饰模式通过将对象放置在包装器类中,然后在运行时动态地向对象添加新的行为或责任,从而实现这一目的。结构装饰模式通常由以下几个部分组成:Component(组件):定义一......
  • Java学设计模式之适配器模式
    一、适配器模式概念1.1什么是适配器模式适配器模式是一种结构型设计模式,它提供了一种将不兼容的接口转化为兼容的接口的方式,从而使原本无法一起工作的类可以协同工作。适配器模式可以分为两种:对象适配器和类适配器。1.2对象适配器对象适配器通过组合的方式,将不兼容的接口适......
  • Java学设计模式之原型模式
    一、原型模式概念原型模式是一种创建型设计模式,其核心思想是通过复制现有对象来创建新对象,而不是通过实例化类来创建。这种方式可以提高创建对象的效率,特别是当对象的创建过程比较昂贵或复杂时。在原型模式中,原型对象是一个已经存在的对象,它作为新对象的模板。新对象通过复制原......
  • 国密算法SM3-java实现
    maven依赖<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.56</version></dependency> SM3Utilsimportorg.bouncycastle.crypto.digests.SM3Dig......
  • 国密算法SM4-java实现
    Maven依赖<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.56</version></dependency>SM4importjava.io.ByteArrayInputStream;importjava.......
  • Java学设计模式之建造者模式
    一、建造者模式概念1.1什么是建造者模式建造者模式是一种创建型设计模式,用于将一个复杂对象的构建过程与其表示分离,以便同样的构建过程可以创建不同的表示。它允许客户端通过相同的构建过程来构建不同的产品。建造者模式通常涉及以下几个角色:产品(Product):表示被构建的复杂对......
  • [LeetCode] 最短的桥 双BFS Java
    Problem:934.最短的桥目录思路复杂度Code思路先找到第一个岛屿,根据每一个岛屿的岛屿块的位置多源查找这个块与第二个岛屿的距离,先找到的就是最少的距离同时,将已遍历过的岛屿标记为-1,避免重复入队复杂度时间复杂度:添加时间复杂度,示例:$O(n^2)$空间复杂度:添......
  • 国密算法SM2-java实现
    Maven依赖<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.56</version></dependency>工具类importjava.math.BigInteger;publicclassUtil{......
  • java测试框架Junit5进阶知识点
    声明参数化导入注解<!--junit5新的编程和扩展模型--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.2</version>......
  • RR级别-多线程环境下-for update+插入操作包含的间隙锁+插入意向锁引发的死锁问题
    记录selectforupdatemysql死锁问题_执行select...where...forupdate是否会造成死锁(deadlock)-CSDN博客......