JavaEE-多线程编程线程池

news2024/11/15 4:54:41

目录

引入线程池的原因

介绍标准库中线程池的参数(高频面试题)

实际开发中,核心线程数设置为多少才合适?

线程池的使用

自己实现一个简单的线程池


像线程池/常量池/内存池/进程池等等,这些池的思想都是一样的——提高效率。

引入线程池的原因

并发编程使用多进程,线程比进程更轻量,在频繁创建销毁方面更有优势,随着时代的发展对频繁有了新的定义,现有的要求下,频繁的创建销毁线程使开销越来越明显了,那么对于这个问题该如何进行优化呢?

(1)线程池 (2)协程(也可以叫纤程,可以理解为比线程更加轻量的程序)

线程池/协程能提高效率的原因:

常规时,线程的创建销毁是要由用户态+内核态配合完成,当引入线程池/协程后可以只由用户态就可以完成,不需要和内核态配合完成。直接调用api创建销毁线程,这个过程需要内核完成,而内核完成的工作很多时候是不太可控的(可能内核还要先做别的工作,这个事情是不可控的),如果使用线程池,提前把线程创建好放到用户态中的数据结构,用的时候直接在线程池中获取,用完了再放回线程池,这个过程完全是用户态代码,不需要和内核进行交互。

协程本质也是纯用户态代码,规避内核操作,不同的是用一个内核的线程来表示多个协程,纯用户态,进行协程之间的调度。

介绍标准库中线程池的参数(高频面试题)

标准库线程池使用ThreadPoolExecutor,使用起来比较复杂,构造方法中包含很多的参数

我们一个一个来看:

(1)corePoolSize 核心线程数,在创建线程池时可以自己设定包含多少核心线程

2)maximumPoolSize 最大线程数,是核心线程+非核心线程的总数,一个线程池刚创建时只包含核心线程数这么多的线程,线程池提供一个submit方法往里面添加任务,每个任务都是一个runnable对象,如果当前的任务较少,当前核心线程可以处理好这些任务,那么此时线程池中就只有核心线程在工作;如果任务较多,核心线程干不过来了,这个时候线程池就会创建新的线程来支撑更多的工作,这个过程称为——动态扩展。新创建的线程数就是非核心线程,新创建的非核心线程加上核心线程不能超过最大线程数。

过了一段时间后任务没那么多了,部分的非核心线程就会被GC回收掉,这样既保证了任务多时工作的效率,也保证了任务少时系统的开销小。

实际开发中,核心线程数设置为多少才合适?

取决于电脑配置(cpu核心数)与程序的实际特点,可以分为两个大类:

1、cpu密集型:代码要完成的逻辑都是要通过cpu干活来完成,比如说如下这段代码:

 int count = 0;
        while(true){
            count++;
        }

形如这种代码逻辑都是在进行逻辑判断/算术运算/循环判定等等,这样的逻辑运行后可以立马吃满一个cpu,如果程序是cpu密集型的,线程数就不应该超过cpu核心数,因为超过了也是浪费。

2、IO密集型:代码大部分时间都在等待IO,等待IO是不消耗cpu的

例如这样的代码:

Thread t1 = new Thread(()->{
            Scanner scanner = new Scanner(System.in);
            int num = scanner.nextInt();
            System.out.println(num);
        });

线程最消耗时间的部分是等待输入。输入输出/sleep/操作硬盘/操作网络等操作都可能会让线程等待IO。

如果你的代码是IO密集型,那它的瓶颈不在cpu上,每个cpu只消耗一点点,线程数取决于其他方面(网络程序/网卡带宽),此时可以在硬件限制下多创建些线程。

小结:以上两种情况都太理性化了,在实际开发中大多是一个程序将cpu密集型与IO密集型都包含了,对于线程数目设置多少最合适这个问题,答案是:根据实际实验找出适合的值,对程序进行性能测试,通过设定不同的线程数,根据实际程序的响应速度和系统开销权衡利弊,最终得到一个你觉得最合适的线程数。

继续分析线程池构造方法的参数列表

(3)keepAliveTime:允许非核心线程空闲时存活的最大时间,数值

(4)Timeunit:上面存活时间的单位

(5)BlockingQueue<Runnable>线程池的任务队列。线程池提供一个submit方法,让线程将任务交给线程池,线程池内部有一个类似队列的数据结构来存储runnable对象,要执行的任务就是runnable对象对应的run方法里的内容。

(6)ThreadFactory:线程工厂。工厂模式也是一种经典的设计模式,主要是为了解决基于构造方法创建对象会产生错误的逻辑。这个线程工厂主要是在批量创建线程时提前设置好了属性,线程工厂在它的方法中提前把线程的属性初始化好了。

重点(7)RejectedExecutionHandler:拒绝策略

假设:如果当前任务队列满了,仍然要添加任务,那么线程池就会给出四种拒绝策略来告诉线程异常,(a)ThreadPoolExecutor.AbortPolicy:直接抛出异常,让程序员知道此时线程池已经满了

(b)ThreadPoolExecutor.CallerRunsPolicy:哪个线程向线程池中添加任务就由哪个线程自己执行,线程池本身不管了。

(c)ThreadPoolExecutor.DiscardOldestPolicy:丢弃最老的任务,将新的任务放到任务队列中排队。

(d)ThreadPoolExecutor.DiscardPolicy:丢弃最新的任务,按照原有的节奏继续执行,无视新任务。

线程池的使用

标准库的ThreadPoolExecutor使用起来比较费劲,标准库自己提供了几个工厂类,对于上述进程池进一步封装了,如果只是想简单使用一下,那工厂类就够了;如果想更精细的控制就使用原生的ThreadPoolExecutor。

这些是标准库提供的创建线程池的工厂方法:

这四个工厂方法的介绍放在了代码的注释中

Executors.newCachedThreadPool(); //创建一个普通线程池,会根据任务数量自行扩容
        Executors.newFixedThreadPool(10);//创建一个固定线程数量的线程池
        Executors.newSingleThreadExecutor();//创建一个只包含单线程的线程池
        Executors.newScheduledThreadPool(10);//创建一个固定线程数量,但延迟执行的线程池

栗子:使用第一种方法创建一个线程池并添加任务

Thread t2 = new Thread(()->{
            ExecutorService service = Executors.newCachedThreadPool();
            for (int i = 0; i < 1000; i++) {
                int count = i;
                service.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(count+" "+Thread.currentThread().getName());
                    }
                });
            }
        });

        t2.start();

截取的一部分运行结果,可以看出自动创建了多个线程来进行该任务。

自己实现一个简单的线程池

一个线程池要有:(1)若干个线程,具体多少个看调用时想设置为多少个 (2)有任务队列,元素为runnable (3)submit方法,将任务添加到任务队列 

第一步:实现自己的线程池类,要有任务队列->BlockingQueue;要有最大线程数与核心线程数,在构造方法中最大线程数就是调用时给定的值,核心线程数需要用循环创建线程,每个线程都要执行任务;submit方法将任务添加到队列中。初步框架已经列好,代码为:

class MyThreadPool{
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
    private int maxPoolSize = 0;
    public MyThreadPool(int corePoolSize,int maxPoolSize){
        this.maxPoolSize = maxPoolSize;
        for (int i = 0; i < corePoolSize ; i++) {
            Thread t = new Thread(()->{
                try {
                    Runnable runnable = queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    }

    void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

第二步:上述代码的问题(1)每个线程创建完成后需要不停的执行工作,所以try catch内的任务应该使用 while循环进行 (2)submit中应该对任务数设置一个阈值,超过这个值说明任务数量太多了需要创建新的线程来工作,但还需要用总线程数来限制,创建的线程数不得大于最大线程数,想记录任务数量可以使用顺序表这样的数据结构来记录数量。优化后的代码为:

class MyThreadPool{
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
    private int maxPoolSize = 0;
    List<Runnable> list = new ArrayList<>();

    public MyThreadPool(int corePoolSize,int maxPoolSize){
        this.maxPoolSize = maxPoolSize;
        for (int i = 0; i < corePoolSize ; i++) {
            Thread t = new Thread(()->{
                try {
                    while (true){
                        Runnable runnable = queue.take();
                        runnable.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
            list.add(t);
        }
    }

    void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
        if(queue.size() >= 100 && list.size() < maxPoolSize){
            Thread t = new Thread(()->{
                while (true){
                    try{
                        Runnable task = queue.take();
                        task.run();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }

                }
            });
            t.start();
            
        }
    }
}

这样,一个简单的类似线程池的代码就创建完了。

道阻且长,行则将至

感谢观看

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

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

相关文章

JS实现文字打印效果(完整代码附效果图)

效果图&#xff1a; 完整代码&#xff1a; <template><view class"page" touchstart"touchstart" touchend"touchend"><view v-if"showTopBlock"><view class"topBlockBg" click"showTopBlockfa…

关于k8s集群中kubectl的陈述式资源管理

1、k8s集群资源管理方式分类 &#xff08;1&#xff09;陈述式资源管理方式&#xff1a;增删查比较方便&#xff0c;但是改非常不方便 使用一条kubectl命令和参数选项来实现资源对象管理操作 &#xff08;2&#xff09;声明式资源管理方式&#xff1a;yaml文件管理 使用yam…

mac下载exe后不自动打开虚拟机

看到网上没有相关教程&#xff0c;正好解决了&#xff0c;发一下 场景&#xff1a;Mac环境下下载EXE文件&#xff0c;会导致VM虚拟机自动打开来执行文件&#xff0c;所以很烦。 解决方法&#xff1a; Mac系统-系统设置-隐私与安全-vmfusion-取消掉下载文件夹即可。 还有其他…

沃尔玛1P账号的强悍作用重要反映在那些方面?——WAYLI威利跨境助力商家

沃尔玛作为全球最大的零售商之一&#xff0c;其品牌影响力非常强大。商家通过入驻沃尔玛商超并开设1P账号&#xff0c;能够借助沃尔玛的品牌影响力来提升自身的品牌知名度和美誉度。这种品牌背书的效应&#xff0c;有助于商家吸引更多的消费者关注和购买自己的产品。 一、沃尔玛…

Android网络编程中的Http协议总结

1.Android与互联网交互的三种方式 2.初识Http协议 实际开发中我们和服务端打交道一般用得都是基于Http协议的通信&#xff0c;所以学好Http协议是非常 重要的&#xff0c;当然&#xff0c;我们不用过于考究一些细节的东西&#xff0c;有个大体的了解即可&#xff01;都是一些概…

6181P-12A2SW71DC触摸屏6181P12A2SW71DC面价

6181P-12A2SW71DC触摸屏6181P12A2SW71DC面价 6181P-12A2SW71DC触摸屏6181P12A2SW71DC面价 6181P-12A2SW71DC触摸屏6181P12A2SW71DC面价 6181P-12A2SW71DC触摸屏6181P12A2SW71DC接线图 6181P-12A2SW71DC触摸屏6181P12A2SW71DC线路图 6181P-12A2SW71DC触摸屏6181P12A2SW71D引…

FPGA知识基础之--clocking wizard ip核的使用以及modelsim与vivado联合仿真

目录 前言一、ip核是什么&#xff1f;1.1 定义1.2 分类 二、为什么使用ip核2.1 ip核的优点2.2 ip核的缺点 三、如何使用ip核&#xff08;vivado&#xff09;四、举例&#xff08;clocking wizard ip核&#xff09;4.1 简介4.2 实验任务4.3 程序设计4.3.1 系统模块4.3.2 波形绘制…

csm兼容性支持模块关闭还是开启_打开csm兼容性模式详细教程

现在新电脑中经常有网友看到bios中有一个csm选项&#xff0c;有些网友不知道是什么意思&#xff0c;其实电脑BIOS中的CSM Support英文全称Compatibility Support Module&#xff0c;翻译成中文就是兼容支持模块&#xff0c;主要是为不支持或不完全支持uefi的操作系统和Legacy模…

如何编写和发布 Python 包

编写和发布Python包是软件开发中非常常见的一项任务。通过创建Python包&#xff0c;开发者可以更好地组织代码&#xff0c;促进代码复用&#xff0c;并且便于共享和分发自己的代码库。 一、准备工作 在开始编写Python包之前&#xff0c;确保你已经安装了以下工具&#xff1a;…

Chainlit快速实现AI对话应用的界面定制化教程

前言 本文主要讲解如何自定义chainlit实现的网页界面的中的一些可以自定修改的样式的实现教程。比如修改自己的logo网站图标或者主题等 翻译 chainlit 默认网页界面显示的是英文&#xff0c;如果我们想显示为其他语言可以进行以下操作。 翻译文件位于项目根目录下的.chainli…

02 部署LVS-DR群集

2.1&#xff1a;直接路由模式&#xff08;LVS-DR&#xff09; 实验环境 关闭所有测试环境的防火墙 [rootbogon ~]# systemctl stop firewalld [rootbogon ~]# setenforce 0 1.配置LVS调速器 &#xff08;1&#xff09;配置虚拟IP地址VIP [rootlvs ~]# cd /etc/sysconfig/n…

53 程序控制结构精彩案例

1 输入若干成绩&#xff0c;求所有成绩的平均分。每输入一个成绩后询问是否继续输入下一个成绩&#xff0c;回答 yes 就继续输入下一个成绩&#xff0c;回答 no 就停止输入成绩。 scores [] # 使用列表存放临时数据 while True:x input(score: )try:scores.append(float(x)…

CEP 复杂事件处理引擎入门:初级高频量价因子策略的实现

高频交易&#xff08;High-Frequency Trading, HFT&#xff09;作为现代金融市场中的重要组成部分&#xff0c;以其高速、自动化和复杂的算法交易策略而著称。高频交易策略通过分析大量实时变化的市场数据&#xff0c;利用市场的微小价格波动迅速做出交易决策&#xff0c;从而在…

【游戏引擎之路】登神长阶(九)——《3D游戏编程大师技巧》:我想成为游戏之神!

5月20日-6月4日&#xff1a;攻克2D物理引擎。 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 6月13日-6月20日&#xff1a;攻克《3D图形教程》。 6月21日-6月22日&#xff1a;攻克《Raycasting游戏教程》。 6月23日-7月1日&#xff1a;攻克《Windows游戏编程大师技巧》。 7月…

浅谈如何将本地代码提交至gitee

文章目录 一、下载git工具二、新建文件夹三、输入命令 [默认提交至master分支] 一、下载git工具 git官网 二、新建文件夹 随便在本机的任意位置新建一个文件夹都行。我以我本机的桌面新建一个文件夹为例&#xff0c;此文件夹可随意命名。 点击该文件夹&#xff0c;右键——…

大奖放送 | AI编程达人秀视频文章征集大赛来啦!

AI Coding&#xff0c;可以有多少种打开玩法&#xff1f;腾讯云AI代码助手是一款辅助编码工具&#xff0c;基于混元大模型&#xff0c;提供技术对话、代码补全、代码诊断和优化等能力&#xff0c;为你生成优质代码&#xff0c;帮你解决技术难题&#xff0c;提升编码效率。 我…

elasticsearch的学习(二):Java api操作elasticsearch

简介 使用Java api操作elasticsearch 创建maven项目 pom.xml文件 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi…

通过率100%!讯方技术河南经贸职业学院华为云计算HCIE订单班取得阶段性成果!

近日&#xff0c;由讯方技术与河南经贸职业学院计算机工程学院联合打造的华为云计算HCIE订单班传来喜讯。在该订单班中&#xff0c;首批参与HCIE实验考试的9名学生凭借扎实的专业知识和优秀的技能水平&#xff0c;全员顺利通过实验考试&#xff0c;通过率达到100%&#xff0c;体…

yolov8 剪枝

yolov8n 初始&#xff1a; YOLOv8n summary (fused): 185 layers, 3151904 parameters, 31936 gradients, 8.7 GFLOPs

正信晟锦:怎么追回欠债多年的钱

在这个世界上&#xff0c;最轻松的或许就是借钱时的许诺&#xff0c;而最难的&#xff0c;莫过于债务回收时的争取。尤其是在牵涉到追回那些欠债多年的旧账时&#xff0c;我们似乎总能更深地体会到“时间是把杀猪刀”的无奈。如何巧妙地追回这些遥不可及的欠款?这既是一场策略…