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

GGBondlctrl 2024-10-09 14:35:03 阅读 82

前言

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

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

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

目录

📚️1.引言 

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

2.1线程池的概念

2.2 线程池的引入

1.引入协程

2.引入线程池

 2.3标准库中的线程池

1.corePoolSize与maximumPoolSize

2.keepAliveTime和TimeUnit

 3.BlockingQueue

4.ThreadFactory

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的使用

创建对象,代码如下:

<code>ExecutorService service= Executors.newFixedThreadPool(4);

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

 具体代码的使用:

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

代码如下:

<code> ExecutorService service= Executors.newFixedThreadPool(3);

service.submit(new Runnable() {

@Override

public void run() {

System.out.println("工厂类创建的线程池对象");

}

});

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

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

代码如下:

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.构造阻塞队列

 代码如下:

BlockingQueue<Runnable> queue=new ArrayBlockingQueue(100);

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

2.构造方法

代码如下:

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方法

代码如下:

public void submit(Runnable runnable) throws InterruptedException {

queue.put(runnable);

}

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

4.主函数

代码如下:

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)

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


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

                             😊😊  期待你的关注~~~



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。