🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇
多线程之线程池
🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇
今日推荐歌曲: Psycho,Pt. 2(Explicit) --Russ 🎵🎵
目录
二. 标准库中的线程池 (ThreadPoolExecutor)
前言
这篇文章将详细介绍可以进一步优化多线程成本的线程池!
一. 线程池是什么(为什么要有线程池)
1.线程池是什么
学到这里,"池"这个概念在很多地方都用到,常量池,数据库,连接池,线程池,进程池,内存池.相信各位友友们应该也不陌生了
主要作用:.
1.提前把要用的对象准备好
2.把用完的对象也不要立即释放,先留着以备下次使用.提高效率!
线程池也是如此:
把要使用的线程提前创建好.用完了也不要直接释放而是以备下次使用.就节省了创建/销毁线程的开销. 在这个使用的过程中,并没有真的频繁创建销毁,而只是从线程池里,取线程使用,用完了还给线程池.
这里举个例子:
想象这么⼀个场景: 在学校附近新开了⼀家快递店,⽼板很精明,想到⼀个与众不同的办法来经营。店⾥没有雇⼈,⽽是 每次有业务来了,就现场找⼀名同学过来把快递送了,然后解雇同学。这个类⽐我们平时来⼀个任 务,起⼀个线程进⾏处理的模式。 很快⽼板发现问题来了,每次招聘+解雇同学的成本还是⾮常⾼的。⽼板还是很善于变通的,知道 了为什么⼤家都要雇⼈了,所以指定了⼀个指标,公司业务⼈员会扩张到3个⼈,但还是随着业务 逐步雇⼈。于是再有业务来了,⽼板就看,如果现在公司还没3个⼈,就雇⼀个⼈去送快递,否则 只是把业务放到⼀个本本上,等着3个快递⼈员空闲的时候去处理。这个就是我们要带出的线程池 的模式。
2.为什么要有线程池
最开始,进程,能够解决并发编程问题,因为频繁创建销毁进程,成本太高了.
引入了轻量级进程=>线程.如果创建销毁线程的频率进一步提高,此时线程的创建销毁开销,也就不能无视了!!
解决方案,有两种:
1.引入轻量级线程=>也称为纤程/协程 Java21里引入的“虚拟线程”就是这个东西
Go是比较早支持协程的(这个概念很多年前就有,但是真正集成到语言中,Go是比较早)Go也是凭借,语法简单,协程,就火了.协程本质,是程序猿在用户态代码中进行调度,不是靠内核的调度器调度的~~ 节省了很多的调度上的开销
2.第二个就是线程池了,线程池最⼤的好处就是减少每次启动、销毁线程的损耗。
二. 标准库中的线程池 (ThreadPoolExecutor)
ThreadPoolExecutor 本身用起来比较复杂.因此标准库还提供了另一个版本,把ThreadPoolExecutor 给封装了一下,就是 Executors 工厂类.通过这个类来创建出不同的线程池对象.(在内部把ThreadPoolExecutor 创建好了并且设置了不同的参数)
• 使⽤Executors.newFixedThreadPool(10)能创建出固定包含10个线程的线程池.
• 返回值类型为ExecutorService
• 通过ExecutorService.submit可以注册⼀个任务到线程池中.
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
2. Executors 创建线程池的⼏种⽅式
• newFixedThreadPool:创建固定线程数的线程池
• newCachedThreadPool:创建线程数⽬动态增⻓的线程池.
• newSingleThreadExecutor: 创建只包含单个线程的线程池.
• newScheduledThreadPool:设定延迟时间后执⾏命令,或者定期执⾏命令.是进阶版的Timer.(定时器)
Executors 本质上是ThreadPoolExecutor类的封装.
三. ThreadPoolExecutor 参数 ⭐⭐⭐
ThreadPoolExecutor 提供了更多的可选参数,可以进⼀步细化线程池⾏为的设定.
• corePoolSize: 核心线程数 正式员⼯的数量.(正式员⼯,⼀旦录⽤,永不辞退)
• maximumPoolSize: 最大线程数 正式员⼯+临时⼯的数⽬.(临时⼯:⼀段时间不⼲活,就被辞退).
• keepAliveTime: 保持存活时间 临时⼯允许的空闲时间.
• unit:keepaliveTime 的时间单位 是秒,分钟,还是其他值.
• workQueue: 线程任务队列 传递任务的阻塞队列 (也可以是阻塞队列 BlockingQueue )
• threadFactory: 创建线程的⼯⼚ 参与具体的创建线程⼯作.通过不同线程⼯⼚创建出的线程相当于 对⼀些属性进⾏了不同的初始化设置.
• RejectedExecutionHandler: 拒绝策略(⭐⭐面试考点) 如果任务量超出公司的负荷了接下来怎么处理.
◦ AbortPolicy(): 超过负荷,直接抛出异常
◦ CallerRunsPolicy():调⽤者负责处理多出来的任务.
◦ DiscardOldestPolicy():丢弃队列中最⽼的任务
. ◦ DiscardPolicy():丢弃新来的任务.
3.1 线程工厂 (详解)
工厂模式,也是一种常见的设计模式, 通过专门的 "工厂类”/“工厂对象” 来创建指定的对象~~通过这个工厂类,来创建线程对象(Thread对象)
举个栗子:
本来应该是这两个方法构成重载,但这里明显不满足重载的要求.
四. 自定义实现线程池
• 核⼼操作为 submit ,将任务加⼊线程池中
• 使⽤ MyThreadPoolExecutor 类描述⼀个⼯作线程.使⽤Runnable描述⼀个任务.
• 使⽤⼀个 BlockingQueue 组织所有的任务
• 每个 t 线程要做的事情:不停的从 BlockingQueue 中取任务并执⾏.
• 指定⼀下线程池中的最⼤线程数 maxWorkerCount ;当当前线程数超过这个最⼤值时,就不再新增线程了.
代码实现:
package Thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class MyThreadPoolExecutor1{
//• 核⼼操作为 submit ,将任务加⼊线程池中
//• 使⽤ ThreadTask 类描述⼀个⼯作线程.使⽤Runnable描述⼀个任务.
//• 使⽤⼀个 BlockingQueue 组织所有的任务
//• 每个 t 线程要做的事情:不停的从 BlockingQueue 中取任务并执⾏.
// • 指定⼀下线程池中的最⼤线程数 maxWorkerCount ;当当前线程数超过这个最⼤值时,就不再新增线程了.
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
//用来存放每个线程以便后续操作
private List<Thread> ThreadList = new ArrayList<>();
//构造方法:创建n个线程不断去执行队列中的任务
public MyThreadPoolExecutor1(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();
ThreadList.add(t);
}
}
//用来接收任务放到队列中
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
}
public class ThreadDemo261 {
public static void main(String[] args) throws InterruptedException {
MyThreadPoolExecutor1 myThreadPoolExecutor1 = new MyThreadPoolExecutor1(10);
for (int i = 0; i < 100; i++) {
int n = i;
myThreadPoolExecutor1.submit(new Runnable() {
@Override
public void run() {
System.out.println("执行任务 " +n+" 当前执行线程:"+Thread.currentThread().getName());
}
});
}
}
}
五. 总结
这里详细叙述的线程池的作用以及实现方法,在找工作面试的时候会经常问到 ThreadPoolExecutor 的参数,大家要好好记住哦 ! ! !
真嘟很详细,手敲原创的 ,希望能帮助大伙,博客不易,点赞 收藏 加关注,知识进脑不迷路!!!