JavaEE 第10节 线程池(Thread Pool)介绍

news2024/9/21 12:38:27

目录

一、线程池是什么

二、为什么线程池中取线程会比直接向操作系统申请来的高效?

三、JAVA标准库中的线程池

(1)类:ThreadPoolExecutor

        1、int corePoolSize与int maximumPoolSize

        2、long keepAlive和TimeUnit unit

        3、BlockingQueue workQueue

        4、ThreadFactory threadFactory

        5、RejectedExecutionHandler handler

(2)类:Excutors

1、提供的方法:

2、返回类类型

四、最简单线程池实现


一、线程池是什么

池(Pool)的引入本质是为了提高程序的运行效率。

线程池(Thread Pool),其核心思想就是提前创建一定数量的线程,形成线程池,当遇到高并发请求的时候,可以直接把任务放到线程池中,让线程池里已经有的线程执行这些任务,避免频繁的创建和销毁线程(系统频繁申请线程开销是比较大的),从而提高效率。

想象一个这样的场景:

你是一名经营着生意火爆的海鲜饭店老板,对新鲜水产品的需求量非常大。所以高效的水产品供应方式一定要想好。

如果直接去市场买,先不谈来回的时间成本,买回来的鱼啊、虾啊、贝壳啊这些可能就不太新鲜了,甚至有的已经嘎了,总之有很多不可控因素在里面

为了解决这种情况,于是你在后厨装修了一个很大的池子,在这个池子中有很多区域被分隔开来,把不同的海鲜产品放到不同的隔间,从而实现更好的管理,最后把那些需要的水产品买回来放到对应隔间即可。等后厨需要的时候直接往池子拿即可。

二、为什么线程池中取线程会比直接向操作系统申请来的高效?

从饭店老板的例子中相信大家已经略知一二了。

这里涉及到操作系统中的概念:

内核态&用户态

操作系统实际上约等于操作系统内核+操作系统配套的应用程序接口

对应的程序的执行很多需要用户态的代码和内核态的代码配合完成的。

但是系统内核是统一调度管理程序的主体,当同时运行多个程序的时候系统内核的工作可能就非常繁忙,此时如果应用程序在去告诉内核它向创建线程,这个线程会被创建,但是这个线程什么时候创建,创建耗时多久就是一个未知数,是不可控制的,极大的影响效率

如图,这样一个形象的例子,狗哥去银行办理业务,但是忘记带复印件了,于是去让柜台人员帮他办理(直接去操作系统申请)但是前面排队的人很多,当他把复印件给柜台人员后,具体什么时候证件复印完,是不知道的,效率就很低:

但是如果是纯用户态的操作,那么效率就会大大提高,如图,狗哥直接去自助银行服务区自行打印并办理业务

三、JAVA标准库中的线程池

一下会介绍两个类,都是用于创建线程池的。

(1)类:ThreadPoolExecutor

官方文档描述:docs.oracle.com

这个类在路径java.util.concurrent这个路径上。

这个类的构造方法有一点复杂,一共有四个构造方法,其中介绍参数最全也是最多的这一个(介绍了这个其他的也就清楚了)

摘自官方文档的构造方法:

一共有7个参数,我们一个一个介绍:

        1、int corePoolSize与int maximumPoolSize

corePoolSize描述的是线程池中至少有几个线程。

当任务请求过多的时候,线程池可以在已有线程的基础上再扩容多几个线程。maximumPoolSize代表的就是线程池最多含有几个线程。

简化记忆:maximumPoolSize=corePoolSize+新创建的线程

        2、long keepAlive和TimeUnit unit

keepAlive描述的是线程池中一个线程的可以空闲的最大时间。

当请求不在繁忙,线程池可能创建了多个线程,对于多创建的且没有实际工作的线程会占用资源,所以要设置一个线程可以“摸鱼”的最大时间,当到达这个时间,那么就销毁这个线程。
而unit指的是时间的单位。
TimeUnit是一个枚举类型,包含多种时间类型:

        3、BlockingQueue<Runnable> workQueue

线程池的使用实际上是典型的生产者消费者模型,用到生产者消费者模型那么就不得不涉及到阻塞队列。
线程池的大致工作流程就是用户端(生产者)通过型如submit的操作把需要完成的任务传递给workQueue(阻塞队列)中,然后线程池(消费者)也通过从workQueue(阻塞队列)拿到需要执行的任务,放入到合适的线程中执行。

        4、ThreadFactory threadFactory

ThreadFactory是一个接口(不是函数式接口),定义如下:

它只有一个抽象方法newThread,这个方法是用来创建线程的,它这里用到了工厂设计模式,是一种常见的设计模式,这种设计模式是用来填构造方法的一个“坑”的

举个例子,要写两个构造方法,确定平面上一个点的位置。这个我们高中学数学的都知道有两种表示方法,一个是用平面直角坐标系(x,y),一个是极坐标(r,θ),但是如果真的这样去定义,编译器就报错了:

通过工厂设计模式就可以避免这种情况发生。

最简单的工厂设计模式就是定义一个静态方法,在这个方法里直接线程创建一个Point类,然后设置类的每一个参数。

如图伪代码:

像这种创建一个对象的静态方法叫“工厂方法”,如果把这类方法单独放到一个类中,这个类就叫“工厂类”

当然在我们实际使用ThreadPoolExecutor类的时候一般不用自己写一个方法去实现这个ThreadFactor接口,一般使用Excutors类(等一下会介绍这个类)自带的 默认方法:Executors.defaultThreadFactory()即可。

        5、RejectedExecutionHandler handler

这个参数描述了无法执行任务时的拒绝策略

什么时候会无法执行任务呢?很简单就是任务队列满的时候。虽然阻塞队列满的时候,submit会进入阻塞状态,但这种方式是不够安全和高效的,因为无法具体控制submit的任务的具体状态。通过给出明确的拒绝策略,是一个明智的办法。


ThreadPoolExecutor提供了四种拒绝策略:

来自JAVA官方文档

1)AbortPolicy(终止策略)

当任务已满,调用者继续发送任务给到任务队列,那么就会抛出RejectedExecutionException


2)CallerRunsPolicy(调用者运行策略)

当任务已满,调用者继续提交任务给任务队列,那么这个任务会由提交这个任务的线程自行完成


3)DiscardOldestPolicy(丢弃最旧任务策略)

当任务已满,调用者继续提交任务给任务队列,那么任务队列中存在任务最旧的任务就会被丢弃(Discard)来优先执行新提交的任务。


4)DiscardPolicy(丢弃策略)

如果任务队列已满,在提交新的任务,直接把刚才新提交的任务丢弃,而不抛出异常或者通知调用者

(2)类:Excutors

1、提供的方法:

ThreadPoolExecutor功能很强大,但参数多,用起来麻烦。

为此,Excutors(工厂类)中提供了许多专门用于构造ThreadPoolExecutor的静态方法,对ThreadPoolExecutor进行了封装,从而简化线程池的使用

这里我们只介绍两个比较常用的构造ThreadPoolExecutor的工厂方法:

  • newFixedThreadPool(size):这个方法规定了创建的线程池固定只有size个线程,也就是核


    心线程数等于最大线程数。

  • newCachedThreadPool():这个方法设置了一个非常大的线程数,线程不够就可以扩容。

2、返回类类型

这些工厂方法都会返回一个接口类型:

这个接口含有许多重要的方法,其中核心的就是service.submit(Runnable r)和service.shutdown()。

1)service.submit(Runnable r)

这个方法可以把对应的任务放到线程池中去执行(重写run方法):

如下代码批量运行,这写任务:

public class ExecutorUse {
    public static void main(String[] args) {

        //蛇者这个线程池固定只有4个线程
        ExecutorService service=Executors.newFixedThreadPool(4);


        for (int i = 0; i <1000 ; i++) {
            int id=i;a            Thread cur= Thread.currentThread();
            service.submit(()->{//直接用lambda表达式,也是可以的
                System.out.println("执行任务id:"+id+","+cur.getName());
            });
        }

    }
}

我们发现程序成功运行完了for循环,但是没有退出:

原因在于虽然提交的任务都运行完了但是ExecutorService默认情况下会一直等待新的任务提交,不会自动关闭。

底层解释就是线程池中所有的线程默认都是前台线程(只要有一个前台线程没有结束,程序就不会结束,即使main线程运行结束),这些前台线程虽然已经干完了活,但是都处于阻塞状态,没有真正终止。

2)service.shutdown()

如果没有需要提交运行的任务了,可以使用这个shutdown()方法关闭线程池中的所有线程

public class ExecutorUse {
    public static void main(String[] args) {

        //蛇者这个线程池固定只有4个线程
        ExecutorService service=Executors.newFixedThreadPool(4);


        for (int i = 0; i <1000 ; i++) {
            int id=i;
            Thread cur= Thread.currentThread();
            service.submit(()->{
                System.out.println("执行任务id:"+id+","+cur.getName());
            });
        }

        //把线程池所有线程终止
        service.shutdown();
    }
}

程序退出:

四、最简单线程池实现

实现一个含有submit的固定线程数目的线程池:

class MyThreadPool_ {

    //定义一个工作队列,把submit的任务放到队列,线程池拿任务,也在这里区,容量顶一个1000
    BlockingQueue<Runnable> workingQueue = new ArrayBlockingQueue<>(1000);


    //设定固定容量的线程的构造方法
    public MyThreadPool_(int capacity) throws InterruptedException {
        for (int i = 0; i < capacity; i++) {

            Thread t = new Thread(() -> {//线程池固定创建capacity个线程
               while(true){//在线程中,死循环,去take任务
                   Runnable r = null;
                   try {
                       r = workingQueue.take();//从工作队列里面获取任务,没有任务会进入阻塞状态(WAITING)
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   r.run();//执行任务
               }
            });
            t.start();//启动线程
        }
    }

    public void submit(Runnable r) throws InterruptedException {
        workingQueue.put(r);//把任务放到工作队列中
    }

}

public class TestCSDNDemo {
    public static void main(String[] args) throws InterruptedException {
        
        //线程池的线程数设置成10个
        MyThreadPool_ myThreadPool=new MyThreadPool_(10);

        //测试,提交1000个任务 
        for (int i = 0; i <1000 ; i++) {
            int id=i;
            myThreadPool.submit(()->{
                Thread currentThread= Thread.currentThread();
                System.out.println("执行任务:"+id+",线程名字:"+currentThread.getName());
            });
        }
    }
}

执行结果:

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

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

相关文章

MySQL第6讲--DQL(数据查询语言)的基本操作之基本和条件查询

文章目录 前言DQL(数据查询语言)基本操作查询操作基本查询示例1&#xff1a;查询表格的name&#xff0c;age&#xff0c;并返回&#xff1b;示例2&#xff1a;查询表格中的所有字段&#xff1b;示例3&#xff1a;查询所有员工的工号并返回&#xff0c;起别名&#xff1b;示例4&…

人工智能在新药研发领域中发挥着至关重要的作用

本综述主要介绍机器学习和深度学习方法在药物发现领域的应用进展以及相关企业。 声明&#xff1a;本文为火石创造原创文章&#xff0c;欢迎个人转发分享&#xff0c;网站、公众号等转载需经授权。 本文选自《药学进展》2021年第7期&#xff0c;作者黄芳 1&#xff0c;杨红飞 1…

武汉流星汇聚:中国卖家亚马逊显威,供应链创新引领全球电商潮

在全球电商的浩瀚星空中&#xff0c;亚马逊无疑是最耀眼的那颗星&#xff0c;其庞大的用户基础、广泛的销售网络以及强大的品牌影响力&#xff0c;为无数商家提供了通往成功的快车道。而在这片充满机遇的蓝海中&#xff0c;中国卖家以其独特的优势&#xff0c;正逐渐成为一股不…

钢铁百科:SA572Gr60应用领域、SA572Gr60热处理状态、SA572Gr60常用规格

一、SA572Gr60材质与执行标准 SA572Gr60钢板是一种美标高强度低合金铌-钒结构钢板&#xff0c;执行标准为ASTM A572/A572M。此外&#xff0c;该钢板也符合ASME标准SA-572/SA-572M。 二、SA572Gr60化学成分 SA572Gr60钢板的主要化学成分包括&#xff1a; 碳C&#xff1a;0.16-…

haproxy高级功能及配置

目录 1.基于cookie的会话保持&#xff1a; 2.HAProxy状态页&#xff1a; 3.IP透传 1.基于cookie的会话保持&#xff1a; cookie value&#xff1a;为当前server指定cookie值&#xff0c;实现基于cookie的会话黏性&#xff0c;相对于基于 source 地址 hash 调度算法对客户端…

msgqueue.hpp队列模块

一.MsgQueue相关类介绍 二.MsgQueue类的实现 成员变量 MsgQueue 结构体用于描述一个消息队列的基本属性。 std::string _name; // 队列名称 bool _durable; // 队列是否持久化 bool _exclusive; // 队列是否独占 bool _auto_del; // 队列是否自动删除 google::pro…

版本控制基础理论

一、本地版本控制 在本地记录文件每次的更新&#xff0c;可以对每个版本做一个快照&#xff0c;或是记录补丁文件&#xff0c;适合个人使用&#xff0c;如RCS. 二、集中式版本控制&#xff08;代表SVN&#xff09; 所有的版本数据都保存在服务器上&#xff0c;协同开发者从…

在HTML中固定表格表头的简单方法

在HTML中&#xff0c;表格元素自身无法提供滚动以及固定表头的配置。借助第三方工具&#xff08;如jQuery的表头固定插件&#xff09;或者结合JavaScrip&#xff0c;是可以实现表格的表头固定的&#xff0c;除此之外&#xff0c;本文还想讨论一种更简单的方式来实现。 从思路上…

【初阶数据结构】详解顺序表(下)(顺序表的代码实现)

文章目录 前言1. 项目文件的配置1.1 顺序表的项目的文件配置(仅供参考) 2. 顺序表的代码实现2.1 SeqList.h&#xff1a;2.2 SeqList.c:2.2.1 顺序表初始化的代码实现&#xff1a;2.2.2 顺序表销毁的代码实现&#xff1a;2.2.3 顺序表尾插数据的代码实现&#xff1a;2.2.4 顺序表…

【国赛必看!】数学建模python基础教学及常用算法代码包分享

一、内容介绍 Python在各个编程语言中比较适合新手学习&#xff0c;Python解释器易于扩展&#xff0c;可以使用C、C或其他可以通过C调用的语言扩展新的功能和数据类型。 Python也可用于可定制化软件中的扩展程序语言。Python丰富的标准库&#xff0c;提供了适用于各个主要系统…

opencv-python图像增强六:低光照增强

文章目录 一&#xff1a;简介二、低光照图像增强方案&#xff1a;三、算法实现步骤3.1 CLAHE直方图均衡化&#xff1a;3.2 伽马变换&#xff1a;3.3 对亮度通道做伽马变换 四&#xff1a;整体代码实现五&#xff0c;效果&#xff1a; 一&#xff1a;简介 低光照图像增强是一种…

Flink 常见问题汇总:反压积压,checkpoint报错,窗口计算,作业报错,无产出,流批不一致,调优等。

Flink 常见问题汇总 0 如何分析日志0.1作业内部重启异常&#xff0c; 作业正常运行0.2 作业内部重启&#xff0c; 但作业已经手动 kill 整个 yarn-application0.3 作业失败&#xff0c;整个 yarn application 结束运行 1 Flink 作业积压排查流程及解决思路1 反压原因2 反压的危…

Windows下搭建Telegraf+Influxdb+Grafana(详解一)

InfluxDB&#xff08;时序数据库&#xff09;&#xff0c;常用使用场景&#xff1a;监控数据统计。 grafana&#xff0c;用作监控页面的前端展示。 telegraf&#xff0c;数据采集器。 所有的安装包都上传到网盘 链接: https://pan.baidu.com/s/1Lv6UnfueK7URx7emAatoYg 提取…

oracle 数据中lsnrctl 是干啥的

突然发现lsnrctl stop 之后&#xff0c;依然可以启动数据库 就感觉怪怪的&#xff0c;一直以为这个是数据库的守护进程&#xff0c;原来不是。。。。 lsnrctl 是 Oracle 监听器控制实用程序的命令行界面工具&#xff0c;用于管理 Oracle Net 服务监听器。监听器是 Oracle 网络…

map和set的应用

map、set 1. 序列式和关联式容器2.set和multiset2.1 构造2.2 迭代器2.3 修改 3. map和multimap3.1 map3.2. multimap 1. 序列式和关联式容器 序列式容器&#xff1a;比如&#xff1a;vector、list、deque、forward_list等&#xff0c;这些容器统称为序列式容器&#xff0c;因为…

Vue3+Echarts+饼图环形图

记得给容器宽高 <div id"leftChartguawang" style"height: 28vh"></div> 配置函数 const leftChartguawang () > {const chartBox echarts.init(document.getElementById(leftChartguawang))let datas [[{ name: 居民节能建筑, value…

SmartEDA电路仿真软件革新力作:重塑电子设计界,揭秘其爆红背后的秘密武器!

在这个日新月异的科技时代&#xff0c;每一场技术革新都是推动行业进步的强劲动力。而在电子设计领域&#xff0c;一款名为SmartEDA的电路仿真软件正以前所未有的姿态&#xff0c;颠覆传统设计模式&#xff0c;成为众多工程师和设计师争相追捧的新宠。今天&#xff0c;就让我们…

硬核详解FutureTask设计与实现

写在文章开头 最近看到一篇比较不错的FutureTask实践,于是对FutureTask源码进行了研究,而本文将从实践和源码两个角度分析FutureTask的设计与实现思路,希望对你有帮助。 Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 …

从零开始搭建 EMQX 集群压测框架

从零开始搭建 EMQX 集群压测框架 架构 在设计以EMQX为中心的MQTT消息队列集群压力测试框架时&#xff0c;我们采用微服务架构模式。EMQX作为消息队列的核心&#xff0c;负责处理MQTT协议的消息发布和订阅。Nginx作为EMQX的反向代理&#xff0c;负责负载均衡和SSL/TLS终端。MQT…