【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)
🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!
💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。
😊😊 期待你的关注~~~
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。