【Java】线程池的概念及使用、ThreadPoolExecutor的构造方法

news2024/12/23 9:00:01

  • 什么是线程池
  • 为什么用线程池
  • JDK提供的线程池
    • 工厂模式
    • 如何使用
  • 自定义线程池
  • ThreadPoolExecutor类的构造方法
    • 工作原理
    • 拒绝策略
  • 线程池的使用

什么是线程池

在之前JDBC编程中,通过DataSource获取Connection的时候就已经用到了的概念。这里的池指的是数据库连接池。当Java程序需要数据库连接的时候就从池子中拿一个空闲的连接对象给Java程序,Java程序用完连接之后就会返回给连接池。线程池就是在池子里放的是线程本身,当程序启动的时候就创建出若干个线程,如果有任务就处理,没任务就阻塞等待
在这里插入图片描述

为什么用线程池

当创建一个线程时,系统申请资源,将其加入到PCB的链表中,当需要销毁线程时释放资源,从PCB链表中移除。线程池的作用就是为了减少这些关于申请和释放PCB的操作,尽量保证程序在用户态执行,减少系统创建线程的开销。

JDK提供的线程池

Executors.newCachedThreadPool():处理大量短时间工作任务的线程池;
Executors.newFixedThreadPool():创建一个固定大小线程池;
Executors.newSingleThreadExecutor(): 创建一个只有一个工作线程的线程池;
Executors.newSingleThreadScheduledExecutor():创建一个带时间定时的线程池;
Executors.newScheduledThreadPool():创建一个指定大小并带时间定时的线程池;
Executors.newWorkStealingPool():创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务。

        // 1. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        // 2. 创建一个操作无界队列且固定大小线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        // 3. 创建一个操作无界队列且只有一个工作线程的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        // 4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。
        ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
        // 5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        // 6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务,不保证处理顺序
        Executors.newWorkStealingPool();

无界队列:指的是对于队列中的元素个数不加限制,可能会出现能存被消耗殆尽的情况。

工厂模式

在JDK中通过Executors工具类调用一系列方法完成不同功能线程池的创建涉及一种设计模式:工厂模式
这种模式就是为了解决构造方法创建对象的不足,比如下面这种情况:
在这里插入图片描述
想通过id和age属性分别创建不同的学生对象,但由于重载方法的参数列表相同,所以会报错。可以定义静态方法起不同的方法名,根据传来的数据按照工厂方法里的逻辑返回对应的对象

class Student {
    private int id;
    private int age;
    private String name;

//    // 通过id和name属性来构造一个学生对象
//    public Student (int id , String name) {
//        this.id = id;
//        this.name = name;
//
//    }
//    // 通过age 和 name 属性来构造一个学生对象
//    public Student (int age , String name) {
//        this.age = age;
//        this.name = name;
//    }

    // 通过id和name属性来构造一个学生对象
    public static Student createByIdAndName (int id, String name) {
        Student student = new Student();
        student.setId(id);
        student.setName(name);
        return student;
    }

    // 通过age 和 name 属性来构造一个学生对象
    public static Student createByAgeAndName (int age, String name) {
        Student student = new Student();
        student.setAge(age);
        student.setName(name);
        return student;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

如何使用

当创建好一个JDK提供的线程池之后,线程池中就存好了一些已创建好的线程,只需要往线程池中提交任务即可。当任务被提交到线程池之后,任务就会被自动执行。

public class Demo03_ThreadPool_Use {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        // 提交任务到线程池
        for (int i = 0; i < 10; i++) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }

        // 模拟等待任务
        TimeUnit.SECONDS.sleep(5);
        System.out.println("第二阶段开始");

        // 提交任务到线程池
        for (int i = 10; i < 20; i++) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }
        
    }
}

自定义线程池

1.可以提交任务到线程池,就需要一种数据结构来保存提交的任务。可以考虑用阻塞队列来保存任务
2.创建线程池时需要指定初始线程数量,这些线程不停扫描阻塞队列,如果有任务就立即执行。可以考虑使用线程池对象的构造方法,接收要创建的线程数据,并在构造方法中完成线程的创建

代码实现

public class MyThreadPool {
    // 1. 定义一个阻塞队列来保存我们的任务
    BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(3);

    // 2. 对外提供一个方法,用来往队列中提交任务
    public void submit (Runnable task) throws InterruptedException {
        queue.put(task);
    }

    // 3. 构造方法
    public MyThreadPool(int capacity) {
        if (capacity <= 0) {
            throw new RuntimeException("线程数量不能小于0.");
        }
        // 完成线程的创建,扫描队列,取出任务并执行
        for (int i = 0; i < capacity; i++) {
            // 创建线程
            Thread thread = new Thread(() -> {
                while (true) {
                    try {
                        // 取出任务(扫描队列的过程)
                        Runnable take = queue.take();
                        // 执行任务
                        take.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            // 启动线程
            thread.start();

        }
    }
}

测试

public class Demo04_MyThreadPool {
    public static void main(String[] args) throws InterruptedException {
        // 创建自定义的线程池
        MyThreadPool threadPool = new MyThreadPool(3);
        // 往线程池中提交任务
        for (int i = 0; i < 10; i++) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }

        // 模拟等待任务
        TimeUnit.SECONDS.sleep(3);
        System.out.println("第二阶段开始");

        // 提交任务到线程池
        for (int i = 10; i < 20; i++) {
            int taskId = i;
            threadPool.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }
    }
}

ThreadPoolExecutor类的构造方法

通过工厂方式获取的线程池,最终都是ThreadPoolExecutor类的对象。打开源码可以看到,要创建这样一个对象,需要传递7个参数
在这里插入图片描述

int corePoolSize :核心线程数,创建线程时包含的最小线程数量
int maximumPoolSize最大线程数,也可以叫临时线程数。当核心线程数不够用的时候,允许系统可以创建的最多线程数
longkeepAliveTime临时线程的空闲时长
TimeUnit unit空闲的时间单位,和keepAliveTime一起使用
BlockingQueue< Runnable >workQueue: 用来保存任务的阻塞队列
ThreadFactory threadFactory线程工厂,如何创建线程,用系统默认的即可
RejectedExecutionHandler handler:拒绝策略,当线程池处理不了过多的任务时会触发

我们用一个场景将这7个参数串起来
在这里插入图片描述

工作原理

1.当任务添加到线程池中时,先判断任务数是否大于核心线程数;
2.如果不大于直接执行,否则加入阻塞队列;
3.当队列满了之后,会按照指定的线程最大数创建临时线程;
4.当阻塞队列满了且临时线程创建完成,再提交任务就会执行拒绝策略。
5.当任务减少,临时线程达到空闲时常时,会被回收。

在这里插入图片描述

⚠️⚠️⚠️注意:当需要创建临时线程的时候,会一次性创建到指定的临时线程数

拒绝策略

API中提供了四种拒绝策略。根据业务场景选择合适的拒绝策略即可。
在这里插入图片描述

线程池的使用

为什么不推荐使用系统自带的线程池?

1.由于使用了无界队列,可能会有内存耗尽的风险
2.创建的临时线程数是整型的最大值(Executors.newCachedThreadPool()),无法把控,也有可能造成资源耗尽
在这里插入图片描述

所以在使用线程池时,我们要根据不同的业务要求指定参数和拒绝策略,规避内存被耗尽的风险

public class Demo05_ThreadPoolExecutor {
    public static void main(String[] args) throws InterruptedException {
        // 创建线程池并指定参数
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy());
        // 添加任务到线程池
        for (int i = 0; i < 10; i++) {
            int taskId = i;
            poolExecutor.submit(() -> {
                poolExecutor.submit(() -> {
                    System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
                });
            });

        }

        // 模拟等待任务
        TimeUnit.SECONDS.sleep(3);
        System.out.println("第二阶段开始");

        // 提交任务到线程池
        for (int i = 10; i < 20; i++) {
            int taskId = i;
            poolExecutor.submit(() -> {
                System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
            });
        }
    }
}

继续加油~

在这里插入图片描述

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

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

相关文章

Vue电商项目--uuid游客身份获取购物车数据

uuid游客身份获取购物车数据 获取购物车列表 请求地址 /api/cart/cartList 请求方式 GET 参数类型 参数名称 类型 是否必选 描述 无 无 无 无 返回示例 成功&#xff1a; { "code": 200, "message": "成功", "…

马尔萨斯 ( Malthus)人口指数增长模型Logistic 模型

3.要求与任务 从 1790 — 1990 年间美国每隔 10 年的人口记录如下表所示&#xff1a; 用以上数据检验马尔萨斯 ( Malthus)人口指数增长模型&#xff0c;根据检验结果进一步讨论马尔萨斯 人口模型的改进&#xff0c;并利用至少两种模型来预测美国2010 年的人口数量。 提示 1 &…

自学黑客(网络安全),一般人我还是劝你算了吧

作为从16年接触网络安全的小白&#xff0c;谈谈零基础如何入门网络安全&#xff0c;有不对的地方&#xff0c;请多多指教。 这些年最后悔的事情莫过于没有把自己学习的东西积累下来形成一个知识体系。 后续我也会陆续的整理网络安全的相关学习资料及文章&#xff0c;与大家一…

数据结构与算法练习(三)二叉树

文章目录 1、树2、二叉树3、满二叉树4、完全二叉树5、二叉树的遍历&#xff08;前序、中序、后序&#xff09;二叉树删除节点或树 6、顺序存储二叉树顺序存储二叉树遍历&#xff08;前序、中序、后序&#xff09; 7、线索化二叉树中序线索二叉树前序线索二叉树后序线索二叉树 1…

Matlab 之 Curve Fitting APP 使用笔记

文章目录 Part.I IntroductionPart.II 使用笔记Chap.I 拟合函数Chap.II 注意事项 Part.I Introduction 曲线或曲面拟合获取拟合参数。本篇博文主要记录一下 Matlab 拟合 APP Curve Fitting 的使用方法。 Part.II 使用笔记 这个APP用来做拟合的&#xff0c;包括二维数据的线拟…

常见的样本统计量及其数字特征

常见的样本统计量及其数字特征 下图来自《统计学图鉴》 样本统计量有什么作用&#xff1f; 因为总体特征包含有总体均值、总体方差等特征&#xff0c;我们在用样本推断总体时&#xff0c;其实就是用样本特征去估计总体特征&#xff0c;例如&#xff1a;样本均值这个统计量的期…

案例33:基于Springboot名城小区物业管理系统开题报告设计

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Spark RDD统计每日新增用户

文章目录 一&#xff0c;提出任务二&#xff0c;实现思路三&#xff0c;准备工作1、在本地创建用户文件2、将用户文件上传到HDFS指定位置 四&#xff0c;完成任务1、在Spark Shell里完成任务&#xff08;1&#xff09;读取文件&#xff0c;得到RDD&#xff08;2&#xff09;倒排…

为什么要对实体类进行序列化并且要生成序列化ID?

一、为什么要对实体类进行序列化且要生成序列化ID 在Java开发中&#xff0c;实体类将会被用来与其他对象进行交互。Java语言是面向对象的&#xff0c;所以实体类包含了很多信息和方法。序列化是Java中一种将对象转换为字节流的机制&#xff0c;使得对象可以在网络上传输和存储。…

相机成像模型(一)

相机模组 如上图所示相机模组由多个元件组成,其中比较重要的元件包括镜头、感光芯片、驱动芯片。镜头的作用是聚集光线,确保良好的成像环境;感光芯片将光信号转换为电信号;驱动芯片则负责信号处理(去噪、白平衡等)与格式转换。 相机的成像过程为物体通过镜头聚集…

jvm cpu 高定位

快速的发现线程cpu高, 最终发现是gc线程, 最终去分析jvm top -o %CPU top -Hp108920 jmap -dump:formatb,fileheap.bin 108920 jvm 命令和工具_个人渣记录仅为自己搜索用的博客-CSDN博客 $ jstat -gcold 108920 MC MU CCSC CCSU OC OU YGC FGC FGCT GCT 218368.0 212670.3 253…

Java POI技术

引入依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.0.1</version> </dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-oo…

Netty的bytebuf详解

ByteBuf ByteBuf是对nio中ByteBuffer的增强。主要的增强点就是ByteBuf它可以动态调整容量大小&#xff0c;当要存储的数据超过了当前容量的上限就会进行扩容&#xff0c;扩容的上限是多少&#xff1f;扩容机制是什么&#xff1f;请跟着本文往下看。对了&#xff0c;还有一个增强…

区间预测 | MATLAB实现基于QRCNN-GRU卷积门控循环单元多变量时间序列区间预测

区间预测 | MATLAB实现基于QRCNN-GRU卷积门控循环单元多变量时间序列区间预测 目录 区间预测 | MATLAB实现基于QRCNN-GRU卷积门控循环单元多变量时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 1.Matlab实现基于QRCNN-GRU卷积神经网络结合门控循…

[BUUOJ] [RE] [ACTF新生赛2020] rome1

IDA 好久没写博客了&#xff0c;最近在刷re&#xff0c;这道题是我觉得十分有意义的一道题。故AC后总结分享给大家。不足之处请指正。 分析 直接导入IDA shift F12 双击后按 ctrl x跳转到被调用的函数中&#xff0c;按F5反编译&#xff0c;源代码如下 int func() {int r…

数据结构-线性表-链表

目录 线性表的链式存储结构&#xff1a;一、单向链表的ADT定义二、链表的优缺点 线性表的链式存储结构&#xff1a; 为了表示数据元素ai和其后继元素ai1之间的逻辑关系&#xff0c;对ai来说需存储其本身信息和后继元素的信息&#xff08;存储位置&#xff09;。这两部分组成ai…

Android系统(AOSP)--编译指令篇

目录 一、编译Android系统 二、普通编译指令 三、快速编译指令 四、新建lunch项和编译类型说明 五、Android编译系统的整体架构 六、编译后的输出目录和生成文件 七、Android常用编译命令总结 一、编译Android系统 1.Android系统全编译(Android5.1以后mtk都是这种方式…

裸辞3个月没工作,害怕面试,害怕HR问我的问题,怎么办?

其实裸辞最大的伤害就是很容易导致自己的不自信。 现在可能就是你的低谷期&#xff0c;你需要做的是什么呢&#xff0c;丰富自己。 你要相信&#xff0c;你只是太久没有面试过&#xff0c;生疏了而已。 今天小月带你回到面试场&#xff0c;找回面试最纯正的感觉&#xff01; 面…

火龙果MM32F3273G8P开发板MindSDK开发教程1 - 点亮LED

火龙果MM32F3273G8P-MindSDK开发教程1-点亮LED 1、登录官网下载对应的MindSDK固件 https://mindsdk.mindmotion.com.cn/&#xff0c;然后注册下载mm32F3270的固件即可。 下载完的文件为 plus-f3270_mdk.zip 解压后的文件路径如图&#xff1a; 2、新建LED工程 将下载的plu…

基于张量补全的交通数据复原文献汇总(最新)

由于传感器故障和通信故障等因素导致的交通数据缺失严重制约了ITS的发展与应用。如何准确、高效地恢复缺失数据已成为ITS的一个关键问题。近年来&#xff0c;LRTC&#xff08;低秩张量补全&#xff09;的方法已被广泛应用于交通数据补全。本文将介绍几篇最新的关于交通数据补全…