异步&线程池
为了节约时间和提高吞吐量,我们要做一些异步请求
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源码
继而new出来返回FutureTask,FutureTask又继承RunnableFuture,而RunnableFuture又分别继承Runnable,Future
所以它的启动方式就是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,还可以传一个对象,让这个对象接受参数
第四种方式
给线程池直接提交任务。
为什么使用线程池?如果我们后来的项目中异步比较多,每次都进行原生代码new Thread().start(),没执行一个线程就开启一个start(存在问题)
我们以后在业务代码里,以上三种启动线程的方式都不能用 避免大量new Thread造成资源消耗完,应该将所有的多线程异步任务交给线程池执行
JUC中最快得到线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//10代表有十个空闲线程 当前系统中池只有一两个,每个异步任务,提交给线程让他自己去执行就行
并不应该是每运行一次就创建一个线程池,应该是一个线程池,大家将所有任务都交给这个线程池来处理
要想访问线程池
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
里面有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)
一个简单面试题:
一个线程池core7 max20 queue 50 100并发进来怎么分配
首先 7个会立即得到执行 50个进入队列 再开13个进行执行 剩下30个采用拒绝策略
如果不想抛弃还要执行 CallerRunsPolicy;同步方法 也可以尝试使用抛弃以前老数据
3,Executors中常见四种线程池
-
newCachedThreadPool() core是0,所有都可以回收
创建一个可缓存线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程
-
newFixedThreadPool() 固定大小 core=max 都不可被回收
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
-
newScheduledThreadPool() 定时任务线程池
创建一个定长线程池,支持定时及周期性任务执行
-
newSingleThreadExecutor() 单线程的线程池,后台从队列里面获取任务 挨个执行
4,开发中为什么使用线程池
-
降低资源的消耗
- 通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
-
提高响应速度
- 因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
-
提高线程的可管理性
- 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁不仅会消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配。
-
CompletableFuture异步编排
业务场景:
查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多时间
假如商品详细页的每个查询,需要如下标注的时间才能完成
那么,用户需要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 接口的实现类,都可以获取线程的执行结果。
CompletableFuture是jdk1.8以后添加的功能 CompletableFuture就类似于vue中promise的ajax请求,执行完了就.then
CompletableFuture提供了四个静态方法来创建一个异步操作
1创建异步对象
public static ExecutorService executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
System.out.println("main....start....");
CompletableFuture<