【JavaEE初阶】深入理解线程池的概念以及Java标准库提供的方法参数分析

前言

🌟🌟本期讲解关于MySQL索引事务,希望能帮到屏幕前的你。

🌈上期博客在这里: 【JavaEE初阶】多线程案列之定时器的使用和内部原码模拟-CSDN博客

🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客

目录

📚️1.引言

📚️2.线程池的基本认知

2.1线程池的概念

[2.2 线程池的引入](#2.2 线程池的引入)

1.引入协程

2.引入线程池

2.3标准库中的线程池

1.corePoolSize与maximumPoolSize

2.keepAliveTime和TimeUnit

3.BlockingQueue

4.ThreadFactory

[5. RejectedExecutionHandler](#5. RejectedExecutionHandler)

📚️3.线程池的基本使用

3.1Executor的使用

3.2线程池中线程数量

3.3线程池的实现

1.构造阻塞队列

2.构造方法

3.submit方法

4.主函数

5.输出

📚️4.总结

📚️1.引言

Hello!!!家人们,国庆是否过得快乐呢??本期小编将讲解关于多线程中比较重要的一个概念,即线程池的概念,以及工厂模式在线程池中的使用,准备好了吗~~~🥳🥳🥳;

且听小编进行讲解,包你学会!!!

📚️2.线程池的基本认知

2.1线程池的概念

线程池是一组预先创建好的线程集合,这些线程处于等待分配任务的状态。当有任务需要执行时,无需创建新线程,而是从线程池中获取一个空闲线程来执行任务。任务完成后,线程不会被销毁,而是返回线程池继续等待下一个任务。

通俗总结:

1.线程池就是一个存储线程的地方,用使用时就从这里取空闲的线程

2.使用过后,线程放回线程池,不被销毁,等待下一个任务

2.2 线程池的引入

最开始,引入了进程,解决了并发编程的问题,但是由于频繁的创建销毁进程,造成了成本消耗提高,此时就引入了线程;

此时又因为线程的频繁创建和开销,所造成的成本消耗是不可以忽视的(抛开剂量谈毒性,都是耍流氓),那么就有以下两种办法

1.引入协程

这里的协程也叫做纤程,即轻量化线程;

本质:协程的本质就是通过用户态代码进行控制的,不是通过内核中的调度器进行调度的;

在用户态代码中协程是通过线程进行封装的可能是N个协程对应一个线程,还有可能是N个协程对应M个线程,一个代码中可以创建很多的协程,但是不能创建很多的线程~~~

2.引入线程池

由于其线程池的概念,可以知道,线程池降低了线程频繁创建销毁的成本消耗,所以就引入了线程池;

为啥线程池取线程比系统中申请线程更高效呢???

假如我们在银行中要进行打印某个证件,我们可以自己去打印或者喊工作人员进行帮助,如下图;

那么此时如果银行人员进行帮助,就会消失在我们的视野,我们不知道 他去干嘛了,此时就是不可控制的,那么对应的效率就比较低;但是当我们自己去打印,就是我们可以控制的,效率高

注意:

从线程池里取线程,是纯用户态的代码(可以控制,效率更高)

通过系统申请线程,是内核态(不可控制,效率更低)

2.3标准库中的线程池

在Java标准库中提供了,关于线程池的构造方法即ThreadPoolExecutor,那么这个方法在Java文档中有很多的参数,小编将一一解释;

1.corePoolSize与maximumPoolSize

corePoolSize:即核心线程数,一个线程池里,最少得有多少个任务;

maximumPoolSize:即最大线程数,一个线程池里,最多有多少个线程;

在标准库提供的线程数目不是一成不变的,这是根据当前的任务的多少来决定的,任务少线程少,任务多线程多,这是一个自适应的过程;

2.keepAliveTime和TimeUnit

这两个是联合进行是使用的,即线程在线程池中空闲状态下能存活多久前面表示数字,后面表示的是时间单位(h,ms,s...)

即线程超过空闲时间后,机会进行销毁,再次使用时再创建一个新的线程;

3.BlockingQueue<Runnable>

这里用runnable作为描述任务的主体,和定时器哪里的是类似的;

4.ThreadFactory

即工厂模式,是一种常见的设计模式,通过专门的工厂类来进行对象的创建,这里就是通过工厂类创建线程对象

这个类提供了方法封装了newThread,给Thread设置了一些属性

5. RejectedExecutionHandler

即拒绝策略,当任务太多,此时线程池有阻塞队列,容纳的任务已经上限了,此时就要进行一些操作,再Java文档中有以下几个策略;

1.第一种

解释:即抛出异常,旧的任务和新的任务都不执行了;

2.第二种

解释:新的任务由引进任务的线程进行执行,线程池不执行这个任务,继续做自己的任务

3.第三种

解释:即线程池舍去一个最老的任务,添加新的任务进行执行

4.第四种

解释:不管这个任务,线程池继续做自己的任务

📚️3.线程池的基本使用

有上述的线程池的介绍中,我们知道ThreadPoolExecutor是非常复杂,参数是比较多多,所以在Java标准库中还有介绍了一种简单的版本

即Executor工厂类,通过这个类创建线程对象,内部已经对 ThreadPoolExecutor 进行了参数的设置,

3.1Executor的使用

创建对象,代码如下:

java 复制代码
ExecutorService service= Executors.newFixedThreadPool(4);

在此时Executors点出来的方法不止一个,如下图:

具体代码的使用:

1.实现Fix表示方法的使用,即规定线程池的线程数量

代码如下:

java 复制代码
 ExecutorService service= Executors.newFixedThreadPool(3);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("工厂类创建的线程池对象");
            }
        });

这里就是表示的是规定的线程数量为3个,submit进行任务的添加;

2.规定时间进行任务的执行

代码如下:

java 复制代码
 ScheduledExecutorService service1=Executors.newScheduledThreadPool(1);
        service1.schedule(()->{
            System.out.println("111");
        },3,TimeUnit.SECONDS);
        service1.shutdown();

这里的1表示的是核心线程数目,这里通过schedule来添加定时执行的任务,3表示数字SECONDS表示的是秒,即三秒后再执行;

3.2线程池中线程数量

这里设计线程池规定的线程数量的时候,是要根据具体的代码来进行分析的;

CPU密集型任务:即线程大部分的时间都在CPU上进行计算;

IO密集型任务:即任务大部分时间都在等待IO不用去CPU上进行计算;

那么此时:如果是所有任务都是CPU密集型任务,那么此时线程数目就不能超过CPU逻辑核心数;反之如全都是IO密集型任务,此时线程数目就可以远远大于逻辑核心数目;(极端的情况下)

解决:尝试给线程池设定不同的线程数目,观察线程时间开销和系统资源占用开销的占比,来找到合适的值; (线程数目增加,时间开销下降,系统资源占用增加,反之则反)

3.3线程池的实现

这里就是四部走的过程:

1.提供构造方法,指定线程数目

2.在构造方法中创建并启动线程

3.阻塞队列的持有的要执行任务

4.submit方法提供执行任务

1.构造阻塞队列

代码如下:

java 复制代码
BlockingQueue<Runnable> queue=new ArrayBlockingQueue(100);

这里的runnable就是任务执行的主体

2.构造方法

代码如下:

java 复制代码
 public MyThreadPoolExecutor(int n){
        for (int i = 0; i <n ; i++) {
            Thread t=new Thread(()->{
                while (true){
                    try {
                        Runnable runnable=queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
        }
    }

解释:这里的n代表包含的线程数目,在外部循环是在线程创建的时候执行,while循环是为了保证线程不断对阻塞队列进行扫描,执行任务,如果为空那么此时就发生阻塞;

3.submit方法

代码如下:

java 复制代码
public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

解释:通过这个方法进行任务的输入,与主函数对应

4.主函数

代码如下:

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor myThreadPoolExecutor=new MyThreadPoolExecutor(4);
        for (int i = 1; i <100 ; i++) {
            int n=i;
            myThreadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("这是线程"+Thread.currentThread().getName()+"执行第"+n+"个任务");
                }
            });
        }
    }

解释:实例化对象后,通过for循环持续进行任务的输入,这里的n是为了防止变量捕获,实现日志的打印;这种方法的执行,小编在上期的定时器的写法类似哦;

5.输出

图片演示如下:

可以看到这里的执行过程不是严格的升序执行的,所以这里即存在任务的抢占;(这里小编只是截取了一部分输出案列)

📚️4.总结

💬💬本期小编主要讲解了关于线程池的基本概念认知,为啥引入线程池,并且线程池中拒绝策略是什么,以及我们如何实现线程池的模拟,也提供了代码供各位uu进行参考~~~;

在线程池的模拟中小编的代码上传Gitee了: GGBondlctrl/Thread (gitee.com)

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

😊😊 期待你的关注~~~

相关推荐
Beekeeper&&P...6 分钟前
git bash是什么,git是什么,git中的暂存区是什么,git中的本地仓库是什么,git中工作目录指的是什么
开发语言·git·bash
NoneCoder11 分钟前
Python入门(12)--数据处理
开发语言·python
周全全14 分钟前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
六月的翅膀17 分钟前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
Domain-zhuo23 分钟前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
SoraLuna24 分钟前
「Mac玩转仓颉内测版24」基础篇4 - 浮点类型详解
开发语言·算法·macos·cangjie
小丁爱养花31 分钟前
前端三剑客(三):JavaScript
开发语言·前端·javascript
生信摆渡1 小时前
R语言-快速对多个变量取交集
开发语言·数据库·r语言
AiFlutter1 小时前
Java实现简单的搜索引擎
java·搜索引擎·mybatis
¥ 多多¥1 小时前
c++中mystring运算符重载
开发语言·c++·算法