目录
一、Future接口理论知识复习
Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消,判断任务执行是否完毕等
案例说明:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙其他事情或者先执行完,过了一会采取获取子任务的执行结果或变更的任务状态
Future接口
可以为主线程开一个分支去做一些耗时的操作,专门为主线程处理耗时和费力的复杂业务
二、Future接口常用实现类FutureTask异步任务
1、Future接口能干什么
Future是java5新加的一个接口,提供了异步并行计算的功能。
如果主线程需要执行一个很耗时的计算任务,我们可以通过future把这个任务放到异步线程中去执行
主线程继续处理其他任务或者先行结束,再通过future获取计算结果
目的:异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务
Runnable接口、Callable接口、Future接口和FutureTask实现类
简单说明:Runnable和Callable接口的区别-> Runnable没有返回值、不会抛出异常;Callable会有返回值会抛出异常
当前存在问题
异步线程需要满足三个特点:多线程、有返回值、异步任务
1、现在 Thread (Runnable target,String name ) 可以满足特点1
2、有返回值则使用callable
3、异步任务则需要实现future接口
所以如果能找到满足以上三个条件的线程是最好的,我们发现FutureTask(Callable<V>callable)可以很好的解决这个问题(FutureTask通过构造注入callable,使得多线程、异步的任务拥有了callable接口的特性,即有返回值)
2、Future接口相关架构
3、FutureTask的使用
正确使用FutureTask接口,如何使用FutureTask来调用callable接口,满足异步线程的规范并带回返回值
public class MyThread_01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//MyThreadCallable myThreadCallable = new MyThreadCallable();
FutureTask<String> callableFutureTask = new FutureTask(new MyThreadCallable());
//FutureTask callableFutureTask = new FutureTask(new MyThreadCallable());
//FutureTask<String> callableFutureTask = new FutureTask<>(new MyThread_02());
Thread t1 = new Thread(callableFutureTask, "t1");
t1.start();
String s = callableFutureTask.get();
System.out.println("s = " + s);
}
}
public class MyThreadCallable implements Callable<String> {
/**
* Runnable 和 Callable 的区别
* 1、是否抛出异常
* 2、是否有返回值
*/
@Override
public String call() throws Exception {
System.out.println(" thread.call");
return "hello"; }
}
4、Future的优缺点
优点:Future+线程池异步多线程任务配合,能显著提高程序的执行效率
如果使用线程池和future获取异步任务的执行结果,则效率只会比单个线程同步执行效率提升1/3;但是如果不获取执行结果,则会比单个线程同步执行效率提升很多倍
public class FutureThreadPool {
/**
* 3个任务,开启多个异步任务来处理,耗时多久
* 不获取结果 花费时间-->1050 效率显著提升
* 获取结果 花费时间-->3048 效率能提升1/3左右
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws Exception {
//花费时间-->4003
//threadWay();
long startTime = System.currentTimeMillis();
//创建线程池,避免多个任务需要多次new 线程导致的资源的浪费,最好使用线程池使得线程做到线程的复用
ExecutorService executorService = Executors.newFixedThreadPool(3);
//Query query = new Query();
//ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1000, TimeUnit.MILLISECONDS, (BlockingQueue<Runnable>) query);
FutureTask<String> stringFutureTask = new FutureTask<String>(()->{
TimeUnit.SECONDS.sleep(2);
return "task1 over";
});
executorService.submit(stringFutureTask);
//System.out.println("1--"+stringFutureTask.get());
FutureTask<String> stringFutureTask2 = new FutureTask<String>(()->{
TimeUnit.SECONDS.sleep(1);
return "task2 over";
});
executorService.submit(stringFutureTask2);
System.out.println("1-- " + stringFutureTask.get());
System.out.println("2-- " + stringFutureTask2.get());
/*MyThreadCallable myThreadCallable = new MyThreadCallable();
Future<String> submit = executorService.submit(myThreadCallable);
System.out.println("3--"+submit.get());*/
TimeUnit.SECONDS.sleep(1);
long endTime = System.currentTimeMillis();
System.out.println("花费时间-->"+(endTime-startTime));
System.out.println(Thread.currentThread().getName()+"\t"+"---end");
executorService.shutdown();
}
/**
* 3个任务,只有一个main来处理,耗时多久 花费时间-->4003
* @throws Exception
*/
public static void threadWay() throws Exception{
long startTime = System.currentTimeMillis();
TimeUnit.SECONDS.sleep(2);
TimeUnit.SECONDS.sleep(1);
TimeUnit.SECONDS.sleep(1);
long endTime = System.currentTimeMillis();
System.out.println("花费时间-->"+(endTime-startTime));
System.out.println(Thread.currentThread().getName()+"\t"+"---end");
}
}
缺点:
1、get方法容易阻塞,一般将get方法都放到程序的最后面,不影响其他程序的工作
package com.juc.thread_future;
import java.util.concurrent.*;
/**
* @description 缺点展示
* @author: shangqj
* @date: 2023/4/11
* @version: 1.0
*/
public class FutureThreadPool2 {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
/**
* 线程池的4种创建方式 todo
*/
//ExecutorService executorService = Executors.newFixedThreadPool(5);
/**
* 创建线程池的核心参数 todo
*
*/
//ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1000, TimeUnit.MILLISECONDS, null);
FutureTask<String> stringFutureTask = new FutureTask<String>(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"come in ");
TimeUnit.SECONDS.sleep(5);
return "task1 over";
});
Thread thread = new Thread(stringFutureTask,"t1");
thread.start();
/**
* 缺点一:get的方法的阻塞
* 此处关于get方法的说明,如果
* System.out.println(Thread.currentThread().getName() + "忙其他的事情---");
*
* System.out.println(stringFutureTask.get());
*
* 执行顺序是这样,则程序是可以被正常高效的执行的
*
* 如果执行顺序是这样
* System.out.println(stringFutureTask.get());
*
* System.out.println(Thread.currentThread().getName() + "忙其他的事情---");
*
* 则程序的main程序不会执行其他任务,会等待get方法执行完成之后才会执行自己的任务
*/
//主线程 执行其他事情
System.out.println(Thread.currentThread().getName() + "忙其他的事情---");
//获取线程的执行结果,容易造成程序的堵塞 一般将get放到最后面处理
/**
* get 小结:
* get容易程序堵塞
* 假如不愿意等待很长时间,可以设置一个等待时间,超过等待时间就会抛出异常,一定程度上避免程序的阻塞
*/
System.out.println(stringFutureTask.get());
System.out.println(stringFutureTask.get(3,TimeUnit.SECONDS)); //-->设置等待时间
}
}
缺点2:isDone()轮询容易导致cpu空转耗费更多的系统资源
我们一般使用get的时候不会单纯的使用get,会和isDone结合使用,
/**
* isDone 方法
* 轮询的方式会耗费无畏的cpu资源,而且不见得能及时的处理计算结果
* 如果想要异步获取结果,通常会以轮询的方式获取结果,尽量不要堵塞
*/
while (true){
if (stringFutureTask.isDone()){
stringFutureTask.get();
System.out.println(stringFutureTask.get());
break;
}else {
//如果没有完成 则每秒钟判断一次是否完成了
TimeUnit.SECONDS.sleep(1);
System.out.println("任务正在执行中");
}
}
其他方法
boolean cancel(boolean mayInterruptIfRunning);// 取消任务
boolean isCanclled();// 是否已经取消
boolean isDone();// 是否已经完成
V get(long timeout,TimeUnit unit);// 超时时间内获取结果
结论:
future接口对于结果的获取方式并不是很友好,只能通过阻塞轮询的方式得到任务的结果
5、对于一些复杂应用
对于简单的业务场景使用future完全是ok的
回调通知
对于future的任务完成之后,可以在完成之后告诉我们,也就是我们的回调通知,future接口是没有办法满足的
通过轮询的方式去判断任务是否完成这样非常占用cpu且代码不够优雅 -> 回调通知代替
异步任务
future + 线程池配合使用
多个任务前后依赖可以组合处理
- 想要将多个异步任务的计算结果结合起来,后一个异步任务的计算洁后果需要前一个异步任务的值
- 将两个或多个异步计算合成一个异步计算的结果,这几个异步任务计算相互独立,同时后面这个又依赖前一个的处理结果
以上两种场景future接口也是没有办法满足的
结论
对于future接口无法满足的复杂应用我们需要使用CompletableFuture接口来满足