🌈个人主页:努力学编程’
⛅个人推荐:
c语言从初阶到进阶
JavaEE详解
数据结构
⚡学好数据结构,刷题刻不容缓:点击一起刷题
🌙心灵鸡汤:总有人要赢,为什么不能是我呢
线程池的概念
我们引入池这个概念,主要是用来提升程序的效率的问题的,这里举一个例子,比方说我是一个集美,此时我和一个小哥哥谈恋爱,随着时间的推移,我逐渐对这个哥哥失去了兴趣,想要重新找一个人玩玩,此时就需要经历两个过程:
1. 和现在的男友分手[耗时间]
2. 重新找一个人培养感情[耗时间]
此时为了简化这个过程,我就可以谈恋爱的时候,同时物色一个备胎对象,当我把现男友玩腻了的时候,直接让备胎上位就好了.大大简化了这个过程,提升了效率
想当初我们引进线程的原因就是因为进程的开销太大了,虽然相对来说线程的开销已经压缩的很小了,但是,随着业务对于性能的要求越来越高时,就会频繁创建和销毁线程,这就无法忽略这个开销了.此时我们便引入了线程池的概念.
操作系统提前将线程创建好,然后集中放到一个地方,后面我们使用的时候,就不必再进行创建了,直接使用线程池中的线程就好了,后面使用完毕后将线程放回到那个指定的位置就好了,这就是线程池的基本概念,
线程池工作原理
对于线程池来说,为什么说比我们从系统创建一个线程要方便很多呢,操作系统中有两个概念:用户态,内核态,操作系统=操作系统的内核+操作系统配套的程序,由于操作系统内核是操作系统中最重要的部分,大部分复杂的代码逻辑都是由内核处理,通常情况下,会以内核态和用户态配合完成这写代码逻辑,当我们提交给操作系统之后,时间就是不可控的,因为操作系统的任务太多了,无法预估.
举一个例子:
我们来银行来办业务.此时发现忘记打印身份证了,此时我们就会有两个选择,1:我们自己去银行中的自主打印机打印,第二,让银行的中的客服给我们去他们的后台打印.
此时我们如果去让银行的客服打印,其实就相当于交给操作系统内核完成,这是不可控的,因为客服的任务工作非常大,可能他要同时处理很多事情,有可能还得先给老板打印资料,后面才能给你打印,效率不高.
如果我们自己去打印,此时就是将代码逻辑完全交给了用户态,此时执行的效率就大大提升了.
Java标准库中线程池的使用
在Java的标准库中给我们已将创建了线程池的类,ThreadExecutor,对于他的构造方法是非常复杂的,这也是一个非常高频的面试题:
构造方法的参数:
这里我们直接讲参数最全的一个版本其他的参数都是在他的基础上做了一些修改:
核心线程数和最大线程数,比如现在有M个线程,工作量突然激增,线程池中的线程已经不够用了,我们需要更多的线程,就会创建一些临时线程,即线程池有自动扩容的功能.
相当于公司会在忙不过来的时候,找一些实习生,等过了这段时间,闲下来就会将这些实习生裁掉.
核心线程:公司的正式员工,不会被轻易裁掉
非核心线程:公司的实习员工,随时有可能会被辞掉.
我们是上面说过了,我们会在线程繁忙的时候,会创建很多除核心线程的临时线程,等到我们需要处理的数据很少的时候,我们就需要将多余的线程回收,此时这两个参数就是用来衡量这些多余的线程可以存活的最大时间,第二个参数代表的是等待时间的单位(秒,分钟…)
我们有了很多的核心线程之后,就需要将这些线程简单封装一下,我们这里使用一个工作队列,将这里线程和任务联系起来,后面,我们具体需要执行某些任务的时候,就可以利用submit方法,将任务添加到工作队列中,然后线程池内部的工作线程执行后序的代码,这里的工作队列,线程池,任务,就好比一个生产者,消费者模型
“工厂”,就是"线程设计工厂",利用这个"工厂",我们可以对线程池中的线程的属性进行批量设置,相对来说比较方便设置多线程场景的属性设置,这里需要注意线程工厂还解决了一个问题,构造方法的局限性,我们在设置构造方法的时候,很多时候需要重构,举个例子.我们之前都学过数学中有两种表示坐标的形式,一个是X,Y轴,另一个是极坐标,这两个的参数我们都需要设置为double类型的数据,但是显然无法满足重构的要求.这是使用线程工厂就可以解决问题.
这是是线程池中最重要的一个参数,当线程池满了的时候,我们就需要对后序发过来的请求,做一个拒绝的处理,这里有几种不同的处理方式,给大家一一介绍一下.
- 对发过来的请求,直接拒绝
2.线程池不处理这个数据,要调用submit的线程执行
3.将任务队列中最老的数据踢掉,执行后序的情求
4.将任务队列中最新的数据踢掉,将执行后序请求
ThreadPoolExecutors 封装
对于 ThreadPoolExecutors 我们上面介绍过了,他的参数非常麻烦,使用起来非常不方便,所以我们对这个类做了进一步的封装,Executors,利用这个参数我们可以方便的完成一些简单的线程池逻辑处理,它提供了很多的工厂方法:
这个 newFixedThreadPool 是不能自动扩容的
Executors模拟实现一个线程池
利用 Executors 完成线程池任务处理
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(4);
for (int i = 0; i < 100; i++) {
int id = i;
service.submit(() -> {
Thread current = Thread.currentThread();
System.out.println("hello thread " + id + ", " + current.getName());
});
}
// 最好不要立即就终止, 可能使任务还没执行完呢, 线程就被终止了.
Thread.sleep(2000);
// 把线程池里所有的线程都终止掉~~
service.shutdown();
System.out.println("程序退出");
}
手撕一个线程池
class MyThreadPool {
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
// 此处 n 表示创建几个线程.
public MyThreadPool(int n) {
// 先创建出 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();
}
}
// 添加任务
public void submit(Runnable runnable) {
try {
queue.put(runnable);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public class Demo31 {
public static void main(String[] args) {
MyThreadPool pool = new MyThreadPool(4);
for (int i = 0; i < 1000; i++) {
int id = i;
pool.submit(() -> {
System.out.println("执行任务" + id + ", " + Thread.currentThread().getName());
});
}
}
}