异步&线程池
为了节约时间和提高吞吐量,我们要做一些异步请求
1、初始化线程的4种方式
1,继承Thread
2,实现Runnable接口
3,实现Callable接口+FutureTask(可以拿到返回结果,可以处理异常)
4,线程池
方式1和方式2:主线程无法获取线程的运算结果。不适合当前场景
方式3:主进程可以获取线程的运算结果,但是不利于控制服务器中的线程资源。可以导致服务器资源耗尽
方式4:通过如下两种方式初始化线程池
Executors.newFiexedThreadPool(3);
//或者
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit,unit,workQueue,threadFactory,handler)
通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是,在业务复杂情况下,一个异步调用可能会依赖于另一个异步调用的执行结果。
方式一实现代码如下:
1,继承Thread
public class ThreadTest {
public static void main(String[] args) {
System.out.println("main....start....");
Thread01 thread01 = new Thread01();
thread01.start();//启动线程
System.out.println("main....end....");
}
public static class Thread01 extends Thread{
@Override
public void run() {
System.out.println("当前线程: "+Thread.currentThread().getId());
int i = 10/2;
System.out.println("运行结果: "+i);
}
}
}
main....start....
main....end....
当前线程:12
运行结果:5
方式二实现代码如下
2,实现Runnable接口
public static class Runable01 implements Runnable{
@Override
public void run() {
System.out.println("当前线程: "+Thread.currentThread().getId());
int i = 10/2;
System.out.println("运行结果: "+i);
}
}
main方法中
System.out.println("main....start....");
Runable01 runable01 = new Runable01();
new Thread(runable01).start();
System.out.println("main....end....");
main....start....
main....end....
当前线程:12
运行结果:5
第三种方式
public static class Callable01 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("当前线程: "+Thread.currentThread().getId());
int i = 10/2;
System.out.println("运行结果: "+i);
return i;
}
}
main方法中
FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
new Thread(futureTask).start();
System.out.println("main....end....");
可以看到Callable接口中直接结束了

所以我们将采取FutureTask的方式来启动Callable的线程,根据FutureTask源码
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Yl5js78-1651988685321)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220505224311186.png)]](https://i-blog.csdnimg.cn/blog_migrate/9a5fef70b00028a46b7b94421817732e.png)
继而new出来返回FutureTask,FutureTask又继承RunnableFuture,而RunnableFuture又分别继承Runnable,Future
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bmbfbJQB-1651988685322)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220505224429176.png)]](https://i-blog.csdnimg.cn/blog_migrate/71762c3e24ee6e25dcdeb8569fbaed82.png)
所以它的启动方式就是FutureTask futureTask = new FutureTask<>(new Callable01());new Thread(futureTask).start();
运行结果如下
main....start....
main....end....
当前线程:12
运行结果:5
Callable最大的好处是FutureTask 我们假设后台运行的很慢,我们还希望后台返回这个Callable的计算结果,就可以使用FutureTask ,它有一个方法.get
等异步方法执行完,返回它的结果
Integer integer = futureTask.get();
//等待整个线程执行完成,获取返回结果
修改main方法
Integer integer = futureTask.get();
System.out.println("main....end...."+integer);
main....start....
当前线程:12
运行结果:5
main....end....+5
所以Integer integer = futureTask.get();是一个阻塞等待
futureTask不仅可以接受Callable,还接受Runnable,如果是Runnable,还可以传一个对象,让这个对象接受参数
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CjrG64pF-1651988685322)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220505225643754.png)]](https://i-blog.csdnimg.cn/blog_migrate/d3c1e202ba8d02523ded313d95223ad2.png)
第四种方式
给线程池直接提交任务。
为什么使用线程池?如果我们后来的项目中异步比较多,每次都进行原生代码new Thread().start(),没执行一个线程就开启一个start(存在问题)
我们以后在业务代码里,以上三种启动线程的方式都不能用 避免大量new Thread造成资源消耗完,应该将所有的多线程异步任务交给线程池执行
JUC中最快得到线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//10代表有十个空闲线程 当前系统中池只有一两个,每个异步任务,提交给线程让他自己去执行就行
并不应该是每运行一次就创建一个线程池,应该是一个线程池,大家将所有任务都交给这个线程池来处理
要想访问线程池
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uR0WvJA8-1651988685323)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220505231118555.png)]](https://i-blog.csdnimg.cn/blog_migrate/d3acc5156570459552fa464b1b919824.png)
里面有submit,可以将一个Runnable或Callable的任务,或者使用execute让它执行
submit跟execute两个区别
如果是submit给线程池中添加一个任务,我们可以获取任务的返回值;如果是execute,直接让线程池执行我们这个操作,返回类型是void 都是给线程池提交异步请求
main中执行
executorService.execute(new Runable01());
main....start....
main....end....
当前线程:12
运行结果:5
这是我们将任务提交给线程池,而不是我们自己在new
给线程池直接提交任务。
* executorService.execute(new Runable01());
* 1,创建
* 1)Executors
* 2)new ThreadPoolExecutor();
* Future:可以获得到异步结果
*
* 区别
* 1,2不能获取返回值3可以获取返回值
* 1,2,3都不能控制资源
* 4,可以控制资源,性能稳定
*
*/
2,线程池的七大参数
/**
* int corePoolSize【5】, 我们的线程数量总是在池中保持着 就算系统当前是空闲的,也是在池总一直有的,等待接收新任务的 除非设置allowCoreThreadTimeOut(允许核心线程超时,这样核心线程空闲就被回收了,否则就不回收)
* 核心线程数[一直存在除非设置allowCoreThreadTimeOut] 线程池 创建好以后准备就绪的线程数量,就等待来接受异步任务去执行 5个 Thread thread = new Thread() thread.start()
int maximumPoolSize, 相当于线程池最大数量;控制资源并发
long keepAliveTime,存活时间 如果当前我们线程数量大于核心线程数的时候 释放空闲线程(maximumPoolSize-corePoolSize) 只要线程空闲大于指定keepAliveTime
TimeUnit unit, 时间单位
BlockingQueue<Runnable> workQueue,阻塞队列 如果任务有很多,就会将目前多的任务放在队列里面 只要有线程空闲,就会去队列里面取出新的任务继续执行
ThreadFactory threadFactory, 线程的创建工厂
RejectedExecutionHandler handler 如果队列满了,按照我们指定的拒绝策略拒绝执行任务(默认使用丢弃策略AbortPolicy)
工作顺序:
1)线程池创建好,准备好core数量的核心线程,准备接受任务
1.1core满了,就将再进来的任务放到阻塞队列中,空闲的core就会自己去阻塞队列获取任务执行
1.2阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量
1.3max满了就用拒绝策略RejectedExecutionHandler拒绝任务
1.4max都执行完成,有很多空闲 在指定的时间【keepAliveTime】以后,释放max-core这些线程
new LinkedBlockingDeque<>():默认是Integer的最大值。内存不够
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
200,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
默认ThreadFactory

拒绝策略(默认使用丢弃策略AbortPolicy)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajzu2oTx-1651988685323)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507220030883.png)]](https://i-blog.csdnimg.cn/blog_migrate/1092aaaf97fb559ad8c89f7184a5f1bf.png)
一个简单面试题:
一个线程池core7 max20 queue 50 100并发进来怎么分配
首先 7个会立即得到执行 50个进入队列 再开13个进行执行 剩下30个采用拒绝策略
如果不想抛弃还要执行 CallerRunsPolicy;同步方法 也可以尝试使用抛弃以前老数据
3,Executors中常见四种线程池
-
newCachedThreadPool() core是0,所有都可以回收
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZDdVdRGS-1651988685324)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507223002518.png)]](https://i-blog.csdnimg.cn/blog_migrate/9313e029d57377f44269b24e25fa27de.png)
创建一个可缓存线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程
-
newFixedThreadPool() 固定大小 core=max 都不可被回收
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tF5seZZV-1651988685324)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507224436089.png)]](https://i-blog.csdnimg.cn/blog_migrate/dc3132b5d9722bc7fd54e6c48a5fad34.png)
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
-
newScheduledThreadPool() 定时任务线程池

创建一个定长线程池,支持定时及周期性任务执行
-
newSingleThreadExecutor() 单线程的线程池,后台从队列里面获取任务 挨个执行
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUipQRDK-1651988685325)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507224810789.png)]](https://i-blog.csdnimg.cn/blog_migrate/4941bc85088d954a0d81b10cf88527d8.png)
4,开发中为什么使用线程池
-
降低资源的消耗
- 通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
-
提高响应速度
- 因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
-
提高线程的可管理性
- 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁不仅会消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配。
-
CompletableFuture异步编排
业务场景:
查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多时间
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBfyMzBf-1651988685326)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507231407566.png)]](https://i-blog.csdnimg.cn/blog_migrate/08532101d8aee73c0fa056623c835bde.png)
假如商品详细页的每个查询,需要如下标注的时间才能完成
那么,用户需要5.5s才能看到商品详细页的内容。很显然是不能接受的
如果多个线程同时完成这6步,也许就只需要1.5s就完成响应
Future是Java5添加的类,用来描述一个异步计算的结果,你可以使用’isDone‘方法检查计算是否完成,或者使用’get‘阻塞猪调用线程,直到计算完成返回结果,你也可以使用’cancel‘方法停止任务执行
虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不 方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的 初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果,为 什么不能用观察者设计模式当计算结果完成及时通知监听者呢? 很多语言,比如 Node.js,采用回调的方式实现异步编程。Java 的一些框架,比如 Netty,自 己扩展了 Java 的 Future接口,提供了addListener等多个扩展方法;Google guava 也提供了 通用的扩展 Future;Scala 也提供了简单易用且功能强大的 Future/Promise 异步编程模式。 作为正统的 Java 类库,是不是应该做点什么,加强一下自身库的功能呢? 在 Java 8 中, 新增加了一个包含 50 个方法左右的类: CompletableFuture,提供了非常强大的 Future 的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以 通过回调的方式处理计算结果,并且提供了转换和组合 CompletableFuture 的方法。 CompletableFuture 类实现了 Future 接口,所以你还是可以像以前一样通过get方法阻塞或 者轮询的方式获得结果,但是这种方式不推荐使用
CompletableFuture 和 FutureTask 同属于 Future 接口的实现类,都可以获取线程的执行结果。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qUka626q-1651988685326)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220508104442345.png)]](https://i-blog.csdnimg.cn/blog_migrate/4c863dc352f900466f3995413ca96a2a.png)
CompletableFuture是jdk1.8以后添加的功能 CompletableFuture就类似于vue中promise的ajax请求,执行完了就.then
CompletableFuture提供了四个静态方法来创建一个异步操作
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Im8GqjtU-1651988685326)(C:\Users\靓仔在此\AppData\Roaming\Typora\typora-user-images\image-20220507233226681.png)]](https://i-blog.csdnimg.cn/blog_migrate/d441e3e39412d8769bb6a922a1b5fa9d.png)
1创建异步对象
public static ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
System.out.println("main....start....");
CompletableFuture<

本文深入探讨了Java中的异步编程,重点讲解了线程池的初始化方式、线程池的七大参数以及Executors中的四种线程池。此外,还详细介绍了CompletableFuture的使用,包括异步对象创建、结果回调、异常处理、线程串行化方法以及多任务组合。使用线程池和CompletableFuture能有效提高系统响应速度,降低资源消耗,实现更高效的异步任务管理。
最低0.47元/天 解锁文章
4882

被折叠的 条评论
为什么被折叠?



