Java开发之Java线程池

news2024/9/20 21:18:23

#来自ゾフィー(佐菲)

1 简介

在需要异步或者并发编程中,常常使用线程池,所谓线程池,就是事先创建好一堆线程,装到一个池子里面,需要用到时候取出来,这样带来了以下的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

2 处理流程

3 Executor 框架

Java 线程既是工作单元,也是执行机制,在 Java 5 之后,引入 Executor 充当执行机制,工作单元由 Runnable 和 Callable 提供。

3.1 结构

  • 任务:需要实现 Runnable 或 Callable 接口。
  • 任务的执行:
    • 执行机制核心接口 Executor
    • 继承 Executor 的 ExecutorService(ThreadPoolExecutor 和 ScheduleThreadPoolExecutor)
  • 异步计算结果:
    • 接口 Future
    • 实现 Future 接口的 FutureTask 类

3.2 使用

  1. 主线程首先创建实现 Runnable 或 Callable 接口的任务对象。
  2. 把任务对象交给 ExecutorService 执行
    • ExecutorService.execute(Runnable task)
    • ExecutorService.submit(Runnable task)
    • ExecutorService.submit(Callable task)
  3. 如果执行 ExecutorService.submit(…),将返回一个实现 Future 接口的对象,由于 FutureTask 实现了 Runnable,我们也可以创建 FutureTask,然后直接交给 ExecutorService 执行。
  4. 主线程可以执行 FutureTask.get() 方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

4 ThreadPoolExecutor

ThreadPoolExecutor 是 Executor 最为核心的类,由四部分组成。

  • corePool:核心线程池的大小。

  • maximumPool:最大线程池的大小。

  • BlockingQueue:用来暂时保存任务的工作队列。

  • RejectedExecutionHandler:当 ThreadPoolExecutor 已经关闭或 ThreadPoolExecutor 已经饱和时(达到了最大线程池大小且工作队列已满),execute() 方法将要调用的 Handler。

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;
    }
  • corePoolSize(线程池的核心线程数基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads() 方法, 线程池会提前创建并启动所有基本线程。
  • runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。

    • ArrayBlockingQueue:基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。

    • LinkedBlockingQueue:基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue,Executors.newFixedThreadPool() 使用了这个队列。

    • SynchronousQueue:不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,Executors.newCachedThreadPool 使用了这个队列。

    • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

  • maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。

  • ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

  • RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,必须采取策略处理提交的新任务。默认情况下是 AbortPolicy,在JDK 1.5中Java线程池框架提供了以下4种策略,也可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。如记录日志或持久化存储不能处理的任务。 。

    • AbortPolicy:直接抛出异常。

    • CallerRunsPolicy:只用调用者所在线程来运行任务。

    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

    • DiscardPolicy:不处理,丢弃掉。

  • keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。

  • TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟 (MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。

5 常用的线程池

5.1 FixedThreadPool

可重用固定线程数的线程池。

//Executors.java
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

FixedThreadPool 的 corePoolSize 和 maximumPoolSize 都被设置为 nThreads。

execute() 执行过程

  • 当前运行的线程数少于 corePoolSize,则创建新线程来执行任务。
  • 当前运行的线程数等于corePoolSize,将任务加入 LinkedBlockingQueue。
  • 线程执行完 1中的任务后,会在循环中反复从 LinkedBlockingQueue 获取任务来执行
5.1.1 注意

FixedThreadPool 使用无界队列 LinkedBlockingQueue(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :

  1. 当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize;
  2. 由于使用无界队列时 maximumPoolSize 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 FixedThreadPool 的源码可以看出创建的 FixedThreadPool 的 corePoolSize 和 maximumPoolSize被设置为同一个值。
  3. 由于 1 和 2,使用无界队列时 keepAliveTime 将是一个无效参数;
  4. 运行中的 FixedThreadPool(未执行 shutdown() 或 shutdownNow())不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。

5.2 SingleThreadExecutor

只有一个线程的线程池。

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

corePoolSize 和 maximumPoolSize 都被设置为 1。

execute() 执行过程

  • 如果当前运行的线程数少于 corePoolSize,则创建一个新的线程执行任务;

  • 当前线程池中有一个运行的线程后,将任务加入 LinkedBlockingQueue;

  • 线程执行完当前的任务后,会在循环中反复从 LinkedBlockingQueue 中获取任务来执行;

5.2.1 注意

使用无界队列 LinkedBlockingQueue 作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。SingleThreadExecutor 使用无界队列作为线程池的工作队列会对线程池带来的影响与 FixedThreadPool 相同,可能会导致 OOM,

5.3 CachedThreadPool

根据需要创建新线程的线程池。

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
 }

corePoolSize 被设置为 0,即 corePool 为空;maximumPoolSize被设置为 Integer.MAX_VALUE,即 maximumPool 是无界的。 keepAliveTime设置为 60L,意味着 CachedThreadPool 中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。

execute() 执行过程

  1. 首先执行 SynchronousQueue.offer(Runnable task) 提交任务到任务队列。如果当前 maximumPool 中有闲线程正在执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行 offer 操作与空闲线程执行的 poll 操作配对成功,主线程把任务交给空闲线程执行,execute() 方法执行完成,否则执行下面的步骤 2;
  2. 当初始 maximumPool 为空,或者 maximumPool 中没有空闲线程时,将没有线程执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤 1 将失败,此时 CachedThreadPool 会创建新线程执行任务,execute 方法执行完成;
  3. 在步骤 2 中新创建的线程将任务执行完后,会执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒钟。如果60 秒钟内主线程提交了一个新任务(主线程执行步骤1)),那么这个空闲线程将执行主线程提交的新任务;否则,这个空闲线程将终止。由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的 CachedThreadPool不会使用任何资源。
5.3.1 注意

允许创建的非核心线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。

5.4 newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务和延迟任务。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
      //ScheduledThreadPoolExecutor
      return new ScheduledThreadPoolExecutor(corePoolSize);
}

5.5 ScheduledThreadPoolExecutor

继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或定期执行任务。

ScheduledThreadPoolExecutor 的执行主要分为两大部分:

  1. 当调用 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate() 方法或者 scheduleWithFixedDelay() 方法时,会向 ScheduledThreadPoolExecutor 的 DelayQueue 添加一个实现了 RunnableScheduledFuture 接口的 ScheduledFutureTask 。
  2. 线程池中的线程从 DelayQueue 中获取 ScheduledFutureTask,然后执行任务。

ScheduledThreadPoolExecutor 为了实现周期性的执行任务,对 ThreadPoolExecutor 做了如下修改:

  • 使用 DelayQueue 作为任务队列;
  • 获取任务的方不同;
  • 执行周期任务后,增加了额外的处理;
5.4.1 定期执行任务

  1. 线程 1 从 DelayQueue 中获取已到期的 ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask 的 time 大于等于当前时间。

  2. 线程1执行这个 ScheduledFutureTask。

  3. 线程1修改 ScheduledFutureTask 的 time 变量为下次将要被执行的时间。

  4. 线程1把这个修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中(DelayQueue.add())。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1940420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用ComfyUI安装可图Kolors大模型做手机壁纸

一、Kolors简介 国内科技公司快手在人工智能领域取得了显著进展&#xff0c;特别推出了「可图 Kolors」这一开源模型&#xff0c;它在图像生成质量上超越了SD3&#xff0c;与Midjourney v6模型相媲美&#xff0c;并支持中文提示词识别与生成中文字符&#xff0c;成为国产AI绘画…

【STM32】理解时钟树(图示分析)

文章目录 时钟系统什么是时钟时钟树简化图示类比示例时钟树详解时钟源系统时钟配置各总线时钟外设时钟 时钟系统 什么是时钟 时钟在电子和计算机系统中指的是生成周期性信号的电路或设备&#xff0c;这种周期性信号用于同步系统内的各种操作。时钟信号通常是方波&#xff0c;…

YOLO 模型基础入门及官方示例演示

文章目录 Github官网简介模式数据集Python 环境Conda 环境Docker 环境部署 CPU 版本官方 CLI 示例官方 Python 示例 任务目标检测姿势估计图像分类 Ultralytics HUB视频流示例 Github https://github.com/ultralytics/ultralytics 官网 https://docs.ultralytics.com/zhhttp…

图像生成(Text-to-Image)发展脉络

这篇博客对 图像生成&#xff08;image generation&#xff09; 领域的经典工作发展进行了梳理&#xff0c;包括重要的一些改进&#xff0c;目的是帮助读者对此领域有一个整体的发展方向把握&#xff0c;并非是对每个工作的详细介绍。 脉络发展&#xff08;时间顺序&#xff0…

13.5.【C语言】二维数组

接第13篇&#xff08;http://t.csdnimg.cn/TioJH&#xff09; 把一维数组做为数组的元素&#xff0c;这时候就是二维数组&#xff0c;二维数组作为数组元素的数组被称为三维数组&#xff0c;二维数组以上的数组统称为多维数组。 01.创建 格式&#xff1a; 数据类型 数组名[…

GESP CCF 图形化编程四级认证真题 2024年6月

一、单选题&#xff08;共 10 题&#xff0c;每题 2 分&#xff0c;共 30 分&#xff09; 题号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 答案 C B C D C D A B D C C D A A B 1、小…

Java语言程序设计基础篇_编程练习题**15.12(几何问题:是否在圆内)

**15.12(几何问题:是否在圆内) 请编写一个程序&#xff0c;绘制一个圆心在(100, 60)而半径为50的固定的圆。当鼠标移动时&#xff0c;显示一条消息表示鼠标点是在圆内还是在圆外&#xff0c;如图15-27a所示 答题思路&#xff1a; 新建一个面板Pane(),一个Circle(100&#xff…

七天打造一套量化交易系统:Day1-数据分类、获取、清洗与存储

七天打造一套量化交易系统&#xff1a;Day1-数据分类、获取、清洗与存储 数据是量化交易的基础&#xff0c;重要性不言而喻。无论是股票、期货、期权、基金、ETF等等&#xff0c;甚至包括比特币&#xff0c;这些投资标的历史行情数据都可以用作回测分析&#xff0c;本篇将分享…

Python测试服务器连接的实战代码

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

2024最佳游戏引擎排行

游戏产业几十年来一直是一个大生意&#xff0c;而且发展势头迅猛。据估计&#xff0c;全球游戏市场在 2025 年每年将达到 5031.4 亿美元&#xff0c;2023 年为 3960 亿美元。 尽管如今有市面上有各种各样的解决方案&#xff0c;但游戏开发人员和管理者在选择适合他们需求的游戏…

【基于netty+zookeeper的rpc远程调用框架】首篇——缘起

&#x1f43c;作者简介&#xff1a;一名大三在校生&#x1f38b; 空有想法&#xff0c;没有实践 文章目录 第一章 概述缘起一、为什么要手写一个rpc项目二、什么是rpc三、rpc怎么使用四、rpc的通信流程 欢迎添加微信&#xff0c;加入我的核心小队&#xff0c;请备注来意 第一章…

NCRE1-2 管理和运营宽带城域网的关键技术

是记录&#xff0c;会有错误 网络管理 这个喜欢考选择题 带内网络管理 用传统的电信网络通过 D C N ( D a t a C o m m u n i c a t i o n N e t w o r k ) DCN(Data\ Communication\ Network) DCN(Data Communication Network)或 P S I N ( P u b l i c S w i t c h T e l …

Java特性与快速入门(JDK,JRE,JVM与hello world)

目录 1.Java重要特点 2.Java运行机制及运行过程 跨平台性 3.什么是JDK,JRE 4.JDK,JRE和JVM的包含关系 5.Java快速入门 输出 hello world&#xff01; 代码示例&#xff1a; 原理讲解&#xff1a; 练习 代码示例&#xff1a; 1.Java重要特点 2.Java运行机制及运行…

自定义Bean转换工具类

BeanConvertor工具类&#xff1a;简化Java对象转换的利器 在Java开发中,我们经常需要在不同的对象之间转换数据。这可能是因为我们需要将数据从一个层(如数据访问层)转移到另一个层(如服务层或表示层),或者是因为我们需要将外部API的数据结构转换为我们的内部数据结构。这种转…

LeetCode 739, 82, 106

文章目录 739. 每日温度题目链接标签思路代码 82. 删除排序链表中的重复元素 II题目链接标签思路代码 106. 从中序与后序遍历序列构造二叉树题目链接标签思路二叉树的三种遍历值与索引的映射对于后序遍历的使用对于中序遍历的使用 代码 739. 每日温度 题目链接 739. 每日温度…

完整创建一个vite前端项目

目录 1.先创建一个vite项目 2.下载第三方依赖 ① 安装路由vue-router ② 安装vuex全局数据管理 ③ 安装element-plus ④ 安装element-plus图标 ⑤ 安装axios发送请求 ⑥ 完整main.js代码模板 3.开发组件 4.登陆页面开发用例 5. 完整项目代码 废话少说&#xff0c;直接…

【C++】 string类的模拟实现

目录 一、我们先创建三个文件分别为 String.h&#xff08;声明&#xff09;、String.cpp&#xff08;定义&#xff09;、teat.cpp&#xff08;测试&#xff09; 二、成员函数 构造函数与析构函数 &#x1f31f;string() &#x1f31f;string(const char* str) &#x1f…

探索SideLlama:浏览器中的本地大型语言模型

在这个数字化时代&#xff0c;浏览器扩展程序已经成为我们日常网络体验中不可或缺的一部分。它们不仅为我们提供了便利&#xff0c;还为我们的浏览体验增添了更多的功能和乐趣。今天&#xff0c;我要介绍的是一个全新的Chrome浏览器扩展程序——SideLlama&#xff0c;它能够让你…

SOMEIPSRV_ONWIRE_03: 从请求中复制请求ID到响应消息

测试目的&#xff1a; 确保服务器在生成响应消息时将请求ID从请求消息复制到响应消息。 描述 本测试用例旨在验证DUT&#xff08;Device Under Test&#xff0c;被测试设备&#xff09;在处理SOME/IP请求和生成相应响应时&#xff0c;是否将请求消息中的请求ID正确复制到了响…

苹果相册里的视频删除了怎么恢复?只需3招,轻松拿捏

一个不小心手滑把苹果手机相册里的视频删除了怎么办&#xff1f;删除了是不是再也找不回来了&#xff1f;那些美好的回忆是不是也从此消散了&#xff1f;当然不是&#xff01;苹果手机相册里的视频删除了怎么恢复&#xff1f;小编这里有3个秘诀&#xff0c;可以让它重新出现在你…