【JavaEE精炼宝库】多线程(6)线程池

目录

一、线程池的概念及优势

1.1 线程池的概念:

1.2 线程池的优势:

二、工厂模式

三、标准库中的线程池

3.1 标准库线程池参数解释:

3.1.1 corePoolSize | maximumPoolSize:

3.1.2 keepAliveTime | unit:

3.1.3 workQueue: 

3.1.4 ThreadFactory: 

3.1.5 handler:

3.2 创建线程池演示:

3.3 利用线程池的工厂类来创建线程池:

3.4 线程池的使用演示:

四、线程池的实现


一、线程池的概念及优势

1.1 线程池的概念:

线程池是一种并发编程的技术,它维护着一组预先创建的线程,以便在需要时重用它们来执行多个任务。这可以提高程序的性能和效率,因为线程的创建和销毁通常是昂贵的操作,通过重用线程,可以减少这种开销。

在我们前面已经介绍了线程比进程更加轻量,在频繁创建销毁的时候,线程更有优势。但是随着时代的发展,对于 “频繁” 有了新的定义(比如之前 1 万就认为是频繁,但是现在可能 1000 万才认为是频繁)。即使是线程,在如此频繁的创建和销毁的情况下,开销也变得难以接受。

那么要怎么优化呢?

答:我们可以通过线程池和协程进行优化。

协程这里我们暂且不进行描述,Go 语言主打的卖点就是使用协程处理并发编程。线程池的优化我们马上讲到。

1.2 线程池的优势:

• 降低资源消耗:

减少线程的创建和销毁带来的性能开销。

• 提高响应速度:

当任务来时可以直接使用,不用等待线程创建

• 可管理性:

进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。

二、工厂模式

在下面即将介绍的线程池参数中,其中有一个参数是工厂类。所以在这里提前给大家解释一下什么是工厂模式。

工厂模式:也是一种设计模式,主要解决的问题是构造方法创建对象太坑了的问题。为什么说构造方法创建对象有坑呢?举个栗子:

因为在 Java 的语法中要求构造方法的命名必须是类名且参数类型和个数,排列顺序不能一样。这就导致遇到上面这种案例解决不了。就需要工厂模式,核心思路,不使用构造方法创建对象,给构造方法包装一层。例如下面这段代码:这只是个伪代码,看看思路就行,makePointByXY 这样的方法就叫 “工厂方法”,这样写代码的套路就叫做 “工厂模式”,没有什么特别的。

public class Point {
    public static Point makePointByXY(double x,double y){
        Point p = new Point();
        p.setX(x);//和 setR 是有区别的,比如传给的函数不一样
        p.setY(y);
        return p;
    }
    public static Point makePointByRA(double r,double a){
        Point p = new Point();
        p.setR(r);
        p.setA(a);
        return p;
    }
}

工厂类的写法如下图: 工厂方法用单独的类来进行提供。

如果语法层面上,不强制要求构造方法名字必须和类名一致,就没有上述模式的必要了。

三、标准库中的线程池

3.1 标准库线程池参数解释:

ThreadPoolExecutor:

使用起来比较复杂,在其构造方法中,包含很多的参数。(面试考点)例如下面这个就是包含全部参数的构造方法,了解这个了,其他的就都会了,下面会围绕这个构造方法参数进行解释。

3.1.1 corePoolSize | maximumPoolSize:

在 Java 的标准库中线程池把线程分成两类:1. 核心线程。2. 非核心线程。

顾名思义 corePoolSize 就是核心线程数,maximumPoolSize 就是最大线程数(核心线程数 + 非核心线程数)。

这里就涉及到动态扩展的概念,在线程池刚开始就会创建出核心线程数的线程(最小线程数),随着项目的进行,如果添加的任务比较多的话,核心线程数处理不过来了(有很多任务在队列中,排队等待执行),这个时候线程池就会自动的创建出新的线程,来支撑更多的任务。但是,创建出来的线程总数不能超过最大线程数(maximumPoolSize),过了一段时间,任务没有那么多了,线程 “清闲下来” 这时部分非核心线程就会被释放掉,线程池中会保证线程数目不少于核心线程数。

这样就既可以保证任务多的时候的效率,也能保证任务少的时候,系统的开销。到这里就又有了一个问题:

在实际开发中,线程数应该设置成多少合适呢?

答:只要是具体的数字,答案一定是错误的。我们要根据实验的方式,对程序进行性能测试,测试过程中,设定不同的线程池的数值。最终根据实际程序的响应速度和系统开销,综合权衡,找到一个合适值。

这不仅仅和电脑的配置有关(有多个 CPU 核心),更重要的是,和我们的程序的实际特点有关系。极端情况下可以分为两大类:

• CPU 密集型程序:代码完成的逻辑,都是要通过 CPU 来完成的.(此时的性能瓶颈是 CPU)。此时线程的数量是不应该超过 CPU 逻辑核心数的。

• IO 密集型程序:代码大部分时间在等待 IO(等待 IO 是不消耗 CPU,不参与调度)。此时瓶颈就不在 CPU 上,我们更多考虑其他方面(比如网络程序,要考虑网卡宽带的瓶颈)。

3.1.2 keepAliveTime | unit:

• keepAliveTime:

表示非核心线程,允许空闲的最大时间。也就是说非核心线程在线程池 ”不忙“ 的时候不是马上回收掉。

• unit:

TimeUnit:这是一个枚举类型。其中的枚举常量有:

3.1.3 workQueue: 

阻塞队列我们上一篇文章已经自己实现过了,这里利用阻塞队列要存储 Runnable 对象也就是要执行的任务。线程池会提供 submit 方法,让其他线程把任务提交给线程池。其他线程会 submit 任务到这个队列中,这个队列存的元素就是 Runnable 对象,要执行的逻辑就是 run 方法里面的内容。

3.1.4 ThreadFactory: 

ThreadFactory:是标准库中提供的,用来创建线程的工厂类。这个线程工厂,主要是为了批量的给创建的线程设置一些属性, 线程工厂在它的工厂方法中,把线程的属性提前初始化好了。至于什么是工厂类,在上面的工厂模式那里已经解释过了,这里就不再赘述。

3.1.5 handler:

面试官问线程池的参数都是什么意思?其实就是在考我们对于这个参数的理解,前面 6 个都是添头。注意:此处 Handler 不是句柄的意思(句柄的术语也是 Handler)。

这里可以理解为拒绝策略,如果当前任务队列满了,仍要继续添加任务,咋办呢?直接阻塞其实不太合适。所以这里多了一个拒绝策略的参数。

幸运的是,我们 Java 的大佬已经给我们实现了四种常用的拒绝策略,在 ThreadPoolExecutor 类的里面,有四种静态内部类,列举如下:

对上面四种拒接策略的简单翻译如下:

 • AbortPolicy():超过负荷,直接抛出异常。

 • CallerRunsPolicy():调用者负责处理多出来的任务。

 • DiscardOldestPolicy():丢弃队列中最老的任务。

 • DiscardPolicy():丢弃新来的任务。

3.2 创建线程池演示:

这里唯一有些难度的就是拒绝策略的创建,在 ThreadPoolExecutor 类中有四个内部类实现了 RejectedExecutionHandler 接口。具体代码如下:

简单的编写如下: 

import java.util.concurrent.*;
public class demo3 {
    public static void main(String[] args) {
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);//阻塞队列
        ThreadFactory factory = new ThreadFactory() {//工厂模式
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);//这里就可以对 Thread 设置一些参数
            }
        };
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();//拒绝策略,注意这里是调用 ThreadPoolExecutor 的静态内部类
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3,6,100,TimeUnit.DAYS,
                queue,factory,handler);
    }
}

3.3 利用线程池的工厂类来创建线程池:

使用线程池的工厂类来创建线程池的方法一共有 6 种,其中我们重点理解下面给出的四种即可。

工厂类为:Executors ,这里产生的线程池为 ExecutorService 类。具体如下图:

可以看到官方提供的创建线程池的工厂方法还是很多的。我们主要理解下面这四个即可。

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class demo1 {
    public static void main(String[] args) {
        //能够根据任务数量,自动进行线程扩容的线程池
        ExecutorService sevice1 = Executors.newCachedThreadPool();
        //创建固定线程数目的线程池
        ExecutorService service2 = Executors.newFixedThreadPool(4);
        //创建只包含单个线程的线程池
        ExecutorService service3 = Executors.newSingleThreadExecutor();
        //创建固定数目的线程个数,但是任务延时执行的线程池
        ExecutorService service4 = Executors.newScheduledThreadPool(4);
    }
}

Executors 创建线程池的几种方式:

• newFixedThreadPool:创建固定线程数的线程池。

• newCachedThreadPool:创建线程数目动态增长的线程池。

• newSingleThreadExecutor:创建只包含单个线程的线程池。

• newScheduledThreadPool:设定延迟时间后执行命令,或者定期执行命令。

Executors 本质上是 ThreadPoolExecutor 类的封装。 

3.4 线程池的使用演示:

 我们可以利用 submit 来进行添加任务(每个都必须实现 Runnable 接口)。

import java.util.concurrent.*;
public class demo1 {
    public static void main(String[] args) {
        ExecutorService service1 = Executors.newCachedThreadPool();
        for(int i = 1;i <= 1000;i++){
            int id = i;//这里必须要使用 id 来接受 i
            service1.submit(new Runnable() {//匿名内部类
                @Override
                public void run() {
                    System.out.println(id +" , " + Thread.currentThread().getName());//必须只能打印 id,打印 i 会报错
                    //报错的原因为:变量捕捉
                }
            });
        }

    }
}

案例演示的结果如下:

至于剩下三个线程池都是一样的,这里就不过多赘述了。 如果只是简单的使用一下,就直接用工厂类即可,如果希望更加精细的控制,就使用原生的 ThreadPoolExecutor。所以 Java 标准库线程池的创建方式一共有 7 种。

四、线程池的实现

为了更加深入的理解线程池的使用,这里我们就来简单的实现一个简易的线程池。我们要实现的东西有1. 若干线程。2. 任务队列。3. 提供 submit 方法。

我们可以利用 ArrayList 来存储线程,利用阻塞队列来辅助完成线程池,创建一个能自动扩容的线程池,每个线程的任务就是不停的去阻塞队列中取元素然后执行。剩下的一些在代码中都有注释。

代码实现如下:

import java.util.concurrent.*;
import java.util.*;
//自己实现一个线程池
class MyThreadPool{
    //1. 线程
    private int corePoolSize;//核心线程数
    private int maximumPoolSize;//最大线程数
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(1000);

    private List<Thread> threads = new ArrayList<>();//存储线程
    public MyThreadPool(int coreThreadSize,int maximumPoolSize) throws InterruptedException {
        this.corePoolSize = coreThreadSize;
        this.maximumPoolSize = maximumPoolSize;
        for(int i = 0;i < coreThreadSize;i++){
            Thread t = newThread();
            t.start();
            threads.add(t);
        }
    }
    private Thread newThread(){
        //每个线程的任务就是不停的执行队列中的元素
        Thread t = new Thread(() -> {
            while(true){
                try {
                    Runnable task = queue.take();
                    task.run();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        return t;
    }
    //2. 阻塞队列
    //3. submit 方法
    public void submit(Runnable task) throws InterruptedException {
        queue.put(task);
        //如果队列中的元素大于一个阈值,且线程数小于最大线程数时,就可以创建新的线程来帮助,处理队列中的元素
        if(queue.size() >= 50 && threads.size() < maximumPoolSize){
            //创建 线程
            Thread t = newThread();
            t.start();
            threads.add(t);
        }
    }
}
public class demo3 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool threadPool = new MyThreadPool(4,50);
        for(int i = 0;i < 10000;i++) {
            int id = i;
            threadPool.submit(new Runnable(){
                @Override
                public void run(){
                    System.out.println("hello " + id + " ," + Thread.currentThread().getName());
                }
            });
        }
    }
}

具体效果如下:

如果要实现拒绝策略的话,也是在 submit 这里实现。 

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue50-mixin混入

一、为什么要使用 mixin混入 两个组件共享一个配置。 二、使用 mixin混入 2-1、创建一个混合js文件 2-2、引入混合js文件 1、局部混合 在每个组件中都引入混合js文件 注意&#xff1a; 混合就是复用配置&#xff0c;vm实例中的所有的配置项&#xff0c;都能在混合.js文件中写…

【计算机毕业设计】基于Springboot的毕业生实习与就业管理系统【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…

新旧torch中傅里叶变换实现(fft)

由泰勒级数我们知道&#xff0c;一个函数可以被分解成无穷个幂函数叠加的形式&#xff0c;于是同样地&#xff0c;一个周期函数也可以被分解成多个周期函数叠加&#xff0c;于是自然而然地&#xff0c;三角函数符合这个需求&#xff0c;由傅里叶级数我们可以将周期函数分解成无…

Qwen2大语言模型微调、导出、部署实践

上篇文章&#xff1a; Qwen1.5大语言模型微调实践_qwen1.5 7b微调-CSDN博客 我们介绍了Qwen1.5 大语言模型使用LLaMA-Factory 来微调&#xff0c;这篇文章我们介绍一下微调后模型的导出、部署。 一、模型导出 在webui 界面训练好模型之后点击“Export”选项卡&#xff0c;然…

linux 部署瑞数6实战(维普,药监局)第一部分

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx 本文章未经许可禁止转载&…

ICML24麻省理工提出使用更少的条件独立性测试来发现因果关系新方法

【摘要】众多科学领域的核心问题围绕着理解因果关系这一基本问题。然而,大多数基于约束的因果发现算法,包括广受欢迎的PC算法,通常会进行指数级数量的条件独立性(CI)测试,在各种应用中造成局限。为解决这一问题,我们的工作重点是表征在减少CI测试数量的情况下,可以了解潜在因果…

POC EXP | woodpecker插件编写

woodpecker插件编写 目录 woodpecker介绍woodpecker使用插件编写 安装环境 woodpecker-sdkwoodpecker-request 创建Maven项目 Confluence OGNL表达式注入漏洞插件编写 创建Package包和Class类编写POC 漏洞POC代码编写导出jar包将jar包放入woodpecker的plugin目录运行woodpeck…

UML与设计模式

1、关联关系 关联关系用于描述不同类的对象之间的结构关系&#xff0c;它在一段时间内将多个类的实例连接在一起。关联关系是一种静态关系&#xff0c;通常与运行状态无关&#xff0c;而是由“常识”、“规则”、“法律”等因素决定的&#xff0c;因此关联关系是一种强关联的关…

MPC质心跟随控制(CoM Tracking Control)

MPC质心跟随 在人形机器人中,质心(CoM)的跟随控制是保持机器人稳定和协调运动的关键技术之一。模型预测控制(MPC)是一种先进的控制方法,通过解决在线优化问题来控制机器人质心的位置和速度。下面我们详细介绍如何使用MPC实现质心跟随控制。 MPC基本原理 模型预测控制是…

Iptables深入浅出

1、iptables的基本概念 众所周知iptables是Linux系统下自带免费的包过滤防火墙。其实不然&#xff0c;iptables其实不是真正的防火墙&#xff0c;我们可以把它理解成一个客户端代理&#xff0c;用户通过iptables这个代理&#xff0c;将用户的安全设定执行到对应的”安全框架”…

微软正在推动 OpenAI 转变为营利性公司!Sam Altman 或拥有更多股权 股东也“逼宫”保时捷

目前&#xff0c;OpenAI估值为860亿美元&#xff0c;转型为营利性公司或加速OpenAI IPO&#xff0c;微软及其他投资者认为&#xff0c;若 Altman拥有更多股权&#xff0c;可能就不会那么有动力专注于其他项目和投资其他AI公司。 根据The Information最新报道&#xff0c;Sam A…

C# TextBox模糊查询及输入提示

在程序中&#xff0c;我们经常会遇到文本框中不知道输入什么内容&#xff0c;这时我们可以在文本框中显示提示词提示用户&#xff1b;或者需要查询某个内容却记不清完整信息&#xff0c;通常可以通过文本框列出与输入词相匹配的信息&#xff0c;帮助用户快速索引信息。 文本框…

java打印helloworld

源代码 public class Function1 {public static void main(String[] args) {System.out.println("hello world");}} 打印结果

llama3-70B体验

NVIDIA LLAMA3-70B大模型体验地址&#xff1a; NVIDIA NIM | llama3-70b 问题几个关于宇宙的问题&#xff0c;答案挺有意思的&#xff0c;很有启发性&#xff0c;记录一下&#xff1a; 问题1&#xff1a;既然相对论认为时间是相对的&#xff0c;为何却说宇宙寿命有137亿年&a…

Luma AI如何注册:文生视频领域的新星

文章目录 Luma AI如何注册&#xff1a;文生视频领域的新星一、Luma 注册方式二、Luma 的效果三、Luma 的优势四、Luma 的功能总结 Luma AI如何注册&#xff1a;文生视频领域的新星 近年来&#xff0c;Luma AI 凭借其在文生视频领域的创新技术&#xff0c;逐渐成为行业的新星。…

如何设计网站

设计网站是一个复杂而又有趣的过程。一个好的网站设计不仅可以吸引用户的注意力&#xff0c;还能提供良好的用户体验。下面我将分享一些关于如何设计网站的基本原则。 首先&#xff0c;需要明确网站的目标和受众。在设计网站之前&#xff0c;你应该明确你的网站的目标是什么。你…

MacOS之Rosetta技术的引入

提示&#xff1a;宝子们&#xff0c;希望文章对你们有所帮助&#xff0c; 请一键三连支持博主下吧&#xff5e; 文章目录 前言一、Rosetta 是什么&#xff1f;二、关于安装Rosetta三、关于Rosetta的问题分享总结 前言 博主的个人开发环境和配置说明&#xff1a; MacOS Montere…

模仿qsort实现一个通用的冒泡排序

目录 前言 模仿 排序整型数组 排序结构体数组 排序字符数组 前言 qsort在前面我们讲到底层逻辑是快速排序的方式&#xff0c;是不是可以发现有了qsort来进行排序的话&#xff0c;就更加的方便快捷&#xff0c;我们在使用的时候 一方面&#xff0c;代码量会大大的减少 另一…

目标检测数据集 - 零售食品LOGO检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;零售食品 LOGO 检测数据集&#xff0c;真实零售食品 LOGO 高质量商品图片数据&#xff0c;数据集含常见零售食品 LOGO 图片&#xff0c;包括饮料类、酒类、调味品类、膨化饼干类、巧克力类、常见零食类等等。数据集类别丰富&#xff0c;标注标签包含 150…

DC/AC电源模块:为电动车充电基础设施提供高效能源转换

BOSHIDA DC/AC电源模块&#xff1a;为电动车充电基础设施提供高效能源转换 DC/AC电源模块是一种用于电动车充电基础设施的重要组件&#xff0c;它能够实现高效能源转换。在电动车的普及和推广过程中&#xff0c;DC/AC电源模块的重要性日益凸显。本文将从DC/AC电源模块的基本原…