线程池的创建方式,为什么阿里推荐自定义线程池?
Java笔记虾 2022-10-11 09:30 发表于山西
点击关注公众号,利用碎片时间学习
1、多线程的四种实现方式
多线程的实现方式有四种
-
继承thread类
-
实现runnable接口
-
实现Callable
-
实现Callable不能简单把Callable对象传给thread,要使用
FutureTask
做一次封装 -
get()
可以获取到call()
返回值 -
get()
阻塞等待所有线程执行完,才输出
-
-
线程池
通常在业务代码中前三种都不使用,只使用第四种(线程池) 每个系统通常有一两个线程池,每一个异步任务,提交给线程池执行即可
1.1 提交任务到线程池的两种方式
-
execute()
返回值为void,代表只执行异步任务,没有返回值 -
submit()
返回值为Future,既可以执行任务,野口接收返回值。
1.2 四种创建线程方式的区别
-
继承thread和实现runnable接口的方式,无法得到返回值,实现callable接口可以得到返回值。
-
前三种方式都无法控制资源,即来一个线程就要创建一个线程,容易使系统资源耗尽
-
线程池的方式可以控制资源,系统性能稳定。
1.3 开发中为什么使用线程池
-
降低资源消耗。 可以重复利用已经创建好的线程,降低线程的创建和销毁带来的损耗。
-
提高响应速度。 因为线程池中的线程都处于等待分配任务状态,当任务过来时可以直接执行。
-
提高线程的可管理性。 如果是单cpu的话,创建多个线程,会导致资源耗尽,但是线程池有拒绝策略;另外还可以核心业务和非核心业务两种线程池,如果某个时间内存压力大,可以释放掉非核心业务线程池。使用线程池就可以使线程的管理方便。
2 、使用ThreadPoolExecutor方式创建线程池
ThreadPoolExecutor
属于原生的创建方式,其他的线程池创建方式都是封装了ThreadPoolExecutor
类。
ThreadPoolExecutor
继承关系图下图
代码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
由以上源码知道,当使用ThreadPoolExecutor
创建线程时,需要传入七大参数!
3.使用Executors创建线程
Executors创建线程,其底层还是用的是ThreadPoolExecutor
创建的。
3.1 Executors创建线程的分类
-
Executors.newFixedThreadPool(10)
:固定大小 core = 自定义的线程数,但阻塞队列是无界队列,会OOM内存溢出
-
Executors.newCachedThreadPool();
core是0,最大线程数无限大,无限创建线程的话,会使cpu占用100%,因为cpu要不停的调度线程去执行任务
-
Executors.newSingleThreadExecutor();
单线程的线程池,后台从队列里取,挨个执行。阻塞队列是无界队列,会OOM内存溢出
-
Executors.newScheduledThreadPool();
带有定时任务的线程池
3 线程池七大参数
int corePoolSize:核心线程数,线程池创建好就已经准备就绪的线程数 量,等待接收异步任务 ,异步任务进来后,自动执行。核心线程会一直存在,除非设置了allowCoreThreadTimeOut
,才允许核心线程超时。
int maximumPoolSize:线程池允许存在的最大线程数
long keepAliveTime:超时时间。如果当前线程数量大于核心数量,且在keepAliveTime
时间内保持空闲,就释放掉。 释放的是最大线程数 - 核心线程数
TimeUnit unit: 超时时间单位
BlockingQueue workQueue:阻塞队列,如果线程有很多,就会把线程保存在队列里 只要线程有空闲,就去阻塞队列中取。 分类见下文。
ThreadFactory threadFactory:线程的创建工厂
RejectedExecutionHandler handler:拒绝策略 ,如果线程满了采取的策略
拒绝策略分类:
-
DiscardOldestPolicy
抛弃掉最早进入的线程 -
DiscardPolicy
抛弃掉最新的线程 -
DiscardPolicy
剩余的线程调用run方法,变为同步执行 -
DiscardPolicy
抛弃掉最新的线程,并抛出异常!
4 线程池的工作顺序
工作顺序
-
线程池创建,准备好核心线程core数,准备接受任务
-
核心线程core满了,就将再进来的任务放入阻塞队列中,空闲的core就回去阻塞队列中获取任务执行
-
阻塞队列满了,就直接开新线程,最大只能开到max执行的数量
-
max线程数满了,采用拒绝策略拒绝新来的任务
-
当任务量减轻,
max - core
剩余的线程在keepAliveTime
时间后,释放掉内存
5. 阻塞队列
5.1 为什么要使用阻塞队列?
我们知道队列是先进先出的。当放入一个元素的时候,会放在队列的末尾,取出元素的时候,会从队头取。那么,当队列为空或者队列满的时候怎么办呢。
这时,阻塞队列,会自动帮我们处理这种情况。
当阻塞队列为空的时候,从队列中取元素的操作就会被阻塞。当阻塞队列满的时候,往队列中放入元素的操作就会被阻塞。
而后,一旦空队列有数据了,或者满队列有空余位置时,被阻塞的线程就会被自动唤醒。
这就是阻塞队列的好处,你不需要关心线程何时被阻塞,也不需要关心线程何时被唤醒,一切都由阻塞队列自动帮我们完成。我们只需要关注具体的业务逻辑就可以了。
5.2 阻塞队列的分类
https://blog.csdn.net/wcc178399/article/details/106663678
6、CompletableFuture异步编排
应用场景:
电商项目中,获取商品详情通常业务比较复杂,还需要调用别的服务的接口,比较耗时。假如每个数据的获取时间如下图所示,如果不使用异步,用户需要5.5s后才能看到页面内容,显然是不合理的。如果使用异步,同时有6个线程同时获取这些数据,用户只需要1.5s即可看到!
CompletableFuture
实现了Future接口,FutureTask
也实现了Future接口
6.1 单任务异步
//自定义一个线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// CompletableFuture的使用
//1.无返回值的异步任务 runAsync()
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
System.out.println("线程号为***" + Thread.currentThread().getId());
int i = 5;
System.out.println("---------" + i);
}, service);
//2.有返回值异步任务 supplyAsync()
CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("线程号为***" + Thread.currentThread().getId());
int i = 5;
System.out.println("---------" + i);
return i;
}, service).whenComplete((r, e) -> {
// whenComplete第一个参数是结果,第二个参数是异常,他可以感知异常,无法返回默认数据
System.out.println("执行完毕,结果是---" + r + "异常是----" + e);
}).exceptionally(u -> {
// exceptionally只有一个参数是异常类型,他可以感知异常,同时返回默认数据10
return 10;
// handler既可以感知异常,也可以返回默认数据,是whenComplete和exceptionally的结合
}).handle((r, e) -> {
if (r != null) {
return r;
}
if (e != null) {
return 0;
}
return 0;
});
6.2 线程串行化
/**
* 3.线程串行化(把后边的线程和前边的串起来)
*/
//thenRunAsync()无返回值
CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("线程号为***" + Thread.currentThread().getId());
int i = 5;
System.out.println("---------" + i);
return i;
}, service).thenRunAsync(() -> {
System.out.println("thenRunAsync,不可接受传来的值,自己无返回值的串行化---");
}, service);
//thenAccept(x)
CompletableFuture<Void> voidCompletableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println("线程号为***" + Thread.currentThread().getId());
int i = 5;
System.out.println("---------" + i);
return i;
}, service).thenAccept((r) -> {
System.out.println("thenAccept可接受传来的值,自己无返回值的串行化---");
});
//thenApply(x)
CompletableFuture<Integer> voidCompletableFuture3 = CompletableFuture.supplyAsync(() -> {
System.out.println("线程号为***" + Thread.currentThread().getId());
int i = 5;
System.out.println("---------" + i);
return i;
}, service).thenApply((r) -> {
System.out.println("thenApply可接受传来的值,自己有返回值的串行化---");
return 10;
});
6.3 两任务组合
/**
* 异步,两任务组合 :两个任务都完成,第三个任务才开始执行
*/
//定义两个任务
//任务一
CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> {
int i = 5;
System.out.println("任务一开始执行" + i);
return i;
}, service);
//任务二
CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
int i = 10;
System.out.println("任务二开始执行" + i);
return i;
}, service);
//要求:任务一、二都完成后才执行任务三
// runAfterBothAsync:无传入值、无返回值
task1.runAfterBothAsync(task2,()->{
System.out.println("任务三开始执行-runAfterBothAsync:无传入值、无返回值 ");
},service);
// thenAcceptBothAsync:有传入值、无返回值
task1.thenAcceptBothAsync(task2,(x,y)->{
System.out.println("任务三开始执行-runAfterBothAsync:无传入值、无返回值 task1的结果是x ,task2的结果是y");
},service);
// thenCombineAsync:有传入值、有返回值
task1.thenCombineAsync(task2,(x,y)->{
System.out.println("任务三开始执行-runAfterBothAsync:无传入值、无返回值 task1的结果是x ,task2的结果是y,task3返回hello");
return "hello";
},service);
/**
* 异步,两任务组合 :两个任务都完成其中一个完成,第三个任务才开始执行
*/
/**
* runAfterEither 无传入值、无返回值
* acceptEither 有传入值、无返回值
* applyToEither 有传入值、有返回值
* 代码同上!
*/
6.4 多任务组合
/**
* 异步,多任务组合 :多个任务都完成,才进行下一步操作
* allOf() 等待所有任务完成
* anyOf() 只要有一个任务完成即可
* 注意:最后使用get()方法:阻塞式等待所有任务都做完,再进行下一步
*/