Future模式是多线程开发中常见的设计模式,它的核心思想是异步调用。对于Future模式来说,它无法立即返回你需要的数据,但是它会返回一个契约,将来你可以凭借这个契约去获取你需要的信息。
应用场景
两个任务没有必然的前后关系,如果在一个线程中串行执行,就有些浪费时间,不如让两个线程去并行执行这两个任务,执行完了到主线程去汇报就可以了。(让任务后台运行,不阻塞线程),具体应用场景和好处我们在上一篇讲解CountDownLatch的使用场景时也有说明。JUC并发编程包CountDownLatch详解https://blog.csdn.net/weixin_37854829/article/details/122604813
实现思路
其实JDK经准备好了一套完美的实现方式。我们用于执行的异步线程只需要实现Callable接口,相当于Runnable,Callable接口中有一个方法call(),相当于run(),在里面写自己的业务。把这个线程放在FutureTask中,这个相当于凭证。把FutureTask交给线程池来进行异步处理,我们可以通过FutureTask中的get()方法返回结果,结果没有返回会阻塞。
1、Callable+FutureTask 获取异步线程结果
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @author youyun.xu
* @Description: FutureTask获取异步线程返回值
* @date 2022/1/23 17:18
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//1、启动异步线程执行任务
//异步线程1(耗时3秒)
Callable<Worker> worker1 = new Callable<Worker>() {
@Override
public Worker call() throws Exception {
Thread.sleep(1000 * 3);
Worker worker = new Worker();
worker.setName("马云");
worker.setPhone("13000000001");
return worker;
}
};
FutureTask futureTask1 = new FutureTask<>(worker1);
new Thread(futureTask1).start();
//异步线程2(耗时4秒)
Callable<Worker> worker2 = new Callable<Worker>() {
@Override
public Worker call() throws Exception {
Thread.sleep(1000 * 4);
Worker worker = new Worker();
worker.setName("马化腾");
worker.setPhone("13000000002");
return worker;
}
};
FutureTask futureTask2 = new FutureTask<>(worker2);
new Thread(futureTask2).start();
//2、模拟主线程处任务
Thread.sleep(1000 * 2);
System.out.println("此处主线程阻塞2秒,模拟处理其他任务");
//3、汇总其他2个异步线程处理结果并打印
List<Worker> list = new ArrayList<>();
list.add((Worker) futureTask1.get());
list.add((Worker) futureTask2.get());
list.parallelStream().forEach(worker -> {
System.out.println("打印结果-> " + worker.toString());
});
}
static class Worker {
private String name;
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
}
2、Callable+Future+CountDownLatch+ExecutorService 获取异步线程结果
结合上面的例子,我们再进行改进,结合Callable+Future+CountDownLatch+ExecutorService一起使用,这样用法才更贴近我们真实使用场景。
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
/**
* @author youyun.xu
* @Description:
* @date 2022/1/23 19:02
*/
public class Demo2 {
/**
* 初始可以缓存线程的线程池,官方提供的线程池没有边界,建议按照需要设置线程值大小,此处仅为演示
*/
final static ExecutorService executorService = Executors.newCachedThreadPool();
static class WorkerHandle implements Callable<Worker> {
private CountDownLatch countDownLatch;
private Worker worker;
public WorkerHandle(CountDownLatch countDownLatch, Worker worker) {
this.countDownLatch = countDownLatch;
this.worker = worker;
}
@Override
public Worker call() throws Exception {
Thread.sleep(1000 * 4);
try {
worker.setAge(new Random().nextInt(10));
} finally {
countDownLatch.countDown();
}
return worker;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CountDownLatch countDownLatch = new CountDownLatch(2);
//线程池依次提交有返回值的异步任务
Future<Worker> worker1 = executorService.submit(new WorkerHandle(countDownLatch, new Worker("马云", "13000000001")));
Future<Worker> worker2 = executorService.submit(new WorkerHandle(countDownLatch, new Worker("马化腾", "13000000002")));
//等待子线程结束
countDownLatch.await();
//获取异步子线程结果
List<Worker> workers = new ArrayList<>();
workers.add(worker1.get());
workers.add(worker2.get());
workers.parallelStream().forEach(worker -> {
System.out.println("打印结果-> " + worker.toString());
});
}
static class Worker {
private String name;
private String phone;
private Integer age;
public Worker(String name, String phone) {
this.name = name;
this.phone = phone;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Worker{" +
"name='" + name + '\'' +
", phone='" + phone + '\'' +
", age=" + age +
'}';
}
}
}
上面例子结合了线程池,闭锁、Future模式等多线程开发中常用的知识,这里为了更好理解,所以代码没有再做抽象,实际编码环境中我们还可以结合设计模式,比如解决get\set我们可以考虑建造者模式,让代码更加优雅。此处仅作演示,只为通俗易懂。