Java多线程:FutureTask CompletableFuture
1. Runnable与Callable
Runnable | Callable | |
---|---|---|
方法 | run() | call() |
返回值 | void | 传入的泛型,推荐使用带超时参数的get方法获取 |
异常 | 内部处理 | 可向上抛 |
使用 | new Thread(Runnable target) | 配合ExecutorService的submit方法 |
2. Future接口和FutureTask
- FutureTask实现Future接口
- Java8的CompletableFuture实现Future接口
- 异步运算,它有启动和取消运算、查询运算是否完成和取回运算结果等方法
- 传入Callable的任务给FutureTask
- get方法将会阻塞
3. ExecutorService几种线程池
线程池 | 作用 |
---|---|
CachedThreadPool | 一个无限扩大可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。比较适合处理执行大量的短期小任务。 |
FixedThreadPool | 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 |
ScheduledThreadPool | 创建一个定长线程池,支持定时及周期性任务执行。可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。 |
SingleThreadExecutor | 一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。 |
WorkStealingPool | 会根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争,底层用的ForkJoinPool来实现的。可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。 |
4. 使用Callable+FutureTask获取执行结果
public class TheTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Task is running!");
Thread.sleep(2000);
int sum = 0;
for (int i=0; i< 10; i++){
sum += i;
}
return sum;
}
}
public class TestFutureTaskAndExecutor {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
TheTask task = new TheTask();
Future<Integer> result = pool.submit(task);
try {
System.out.println("result is " + result.get(5, TimeUnit.DAYS));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
pool.shutdown();
}
}
5. CompletableFuture
5.1 CompletableFuture中4个异步执行任务静态方法
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
supplyAsync用于有返回值的任务,runAsync则用于没有返回值的任务。Executor参数可以手动指定线程池,否则默认ForkJoinPool.commonPool()系统级公共线程池
CompletableFuture,默认依靠fork/join框架启动新的线程实现异步与并发的。
它提供了函数式编程的能力,可以通过回调函数的方式处理返回结果,并且提供了转换和组合CompletableFuture的方法。
在用CompletableFuture之前,我认为得先熟悉下Java8的Lambda编程语法,《Java8实战》这本书非常适合阅读。
CompletableFuture创建线程有2种方式:supplyAsync(有返回值)和:runAsync(无返回值)。
5.2 创建测试用的业务类
创建一个DeptService,模拟根据Id获取部门的方法getById(Integer id)。
public class DeptService {
public Dept getById(Integer id) {
System.out.println("线程:" + Thread.currentThread().getName() + " getById(" + id + ")");
if (id == 1){
return new Dept(1, "研发一部");
} else if (id == 2){
return new Dept(2, "研发二部");
} else {
throw null;
}
}
}
创建一个UserService ,模拟getById()和save()这2个方法。
public class UserService {
//根据Id获取User
public User getById(Integer id) throws Exception {
System.out.println("线程:" + Thread.currentThread().getName() + " getById(" + id + ")");
if (id == 1){
return new User(1, "冬哥", 31);
} else if (id == 2){
return new User(2, "珣爷", 30);
} else {
throw new Exception("未能找到人员");
}
}
//保存User
public User save(User user){
System.out.println("线程:" + Thread.currentThread().getName() + " save()," + user.toString());
return user;
}
}
5.3 supplyAsync(有返回值)
supplyAsync有2种,第二个需要多传如线程池的实现。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
Executor executor)
需求:
- 在main线程里创建一个线程异步获取id=1的部门
- 在main线程里获取新线程的返回值
supplyAsync()中调取deptService.getById(1),并return它的值。
public class Thread01_SupplyAsync {
public static void main(String[] args) throws ExecutionException, InterruptedException {
DeptService deptService = new DeptService();
CompletableFuture<Dept> deptCompletableFuture = CompletableFuture.supplyAsync(() -> {
Dept dept = deptService.getById(1);
return dept;
});
System.out.println("线程:" + Thread.currentThread().getName() +
" 结果:" + deptCompletableFuture.get());
}
}
运行结果如下,可见DeptService是在新的线程里执行的,而在main主线程中调用get()获取线程的结果会阻塞主线程。
线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main 结果:Dept{id=1, name='研发一部'}
5.4 runAsync(无返回值)
runAsync适用无返回值的情况,也有2种,第二个需要多传如线程池的实现
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable,
Executor executor)
对上面SupplyAsync的测试代码稍做修改,runAsync()里没有返回值,所以去掉return。
public class Thread02_RunAsync {
public static void main(String[] args) throws ExecutionException, InterruptedException {
DeptService deptService = new DeptService();
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
deptService.getById(1);
});
System.out.println("线程:" + Thread.currentThread().getName() +
" 结果:" + voidCompletableFuture.get());
}
}
运行结果如下,可以看出runAsync也是创建了新线程,调用get方法只能返回null。
线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main 结果:null
6. 串行的回调函数
当子线程完成后,需要调用一些回调方法,如果是Java8以前,我们写的会稍微复杂。
Java8的CompletableFuture已经为我们实现了几个回调函数,使用非常方便。
1. thenApply 转换结果
apply有“申请”、“应用”的意思,我个人理解为把上一个线程的结果“应用于”下一个线程的计算。相当于结果值的传递。
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
其中thenApply是同步的,thenApplyAsync是异步的。
Function<? super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型
需求:
- 在main线程里创建一个线程异步获取id=1的部门
- 将上面线程的返回值传递给下一个任务:给user赋值部门信息,并保存user
- 在main线程获取保存后user的值
public class Thread03_SupplyAsync_ThenApply {
public static void main(String[] args) throws ExecutionException, InterruptedException {
DeptService deptService = new DeptService();
UserService userService = new UserService();
User user = new User(1, "冬哥", 31);
CompletableFuture<User> userCompletableFuture = CompletableFuture.supplyAsync(() -> {
Dept dept = deptService.getById(1);
return dept;
})
.thenApplyAsync(dept -> {
//注意这里用到了上个线程的返回值dept
user.setDeptId(dept.getId());
user.setDeptName(dept.getName());
return userService.save(user);
});
System.out.println("线程:" + Thread.currentThread().getName() +
" 结果:" + userCompletableFuture.get().toString());
}
}
运行结果如下:
线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:ForkJoinPool.commonPool-worker-1 save(),User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
线程:main 结果:User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
如果将thenApplyAsync()替换成thenApply(),第二个任务将在主线程中同步执行,结果如下:
线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main save(),User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
线程:main 结果:User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
2. thenAccept 消费结果
thenAccept 同 thenApply 接收上一个任务的返回值作为参数,但是回调方法无返回值。
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
需求:
- 在main线程里创建一个线程异步获取id=1的部门
- 将上面线程的返回值dept传递给下一个任务:dept作为日志记录发给Kafka
public class Thread04_SupplyAsync_ThenAccept {
public static void main(String[] args) throws ExecutionException, InterruptedException {
DeptService deptService = new DeptService();
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
Dept dept = deptService.getById(1);
return dept;
})
.thenAcceptAsync(dept -> {
//注意这里用到了上个线程的返回值dept
System.out.println("线程:" + Thread.currentThread().getName() +
"假设把dept作为日志记录发给Kafka: " + dept.toString());
//thenAccept是没有返回值的
});
System.out.println("线程:" + Thread.currentThread().getName() +
" 结果:" + voidCompletableFuture.get());
}
}
运行结果如下:
线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:ForkJoinPool.commonPool-worker-1把dept作为日志记录发给Kafka: Dept{id=1, name='研发一部'}
线程:main 结果:null
如果thenAcceptAsync(异步)改成thenAccept(同步),结果如下:
线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main把dept作为日志记录发给Kafka: Dept{id=1, name='研发一部'}
线程:main 结果:null
3. thenRun 任务完成后触发的回调
thenRun 是上一个任务完成后触发的回调,没有入参,也没有返回值。
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
需求:
- 在main线程里创建一个线程异步获取id=1的部门
- 上面线程结束后,执行thenRun里的回调,没有入参和返回值
public class Thread05_SupplyAsync_ThenRun {
public static void main(String[] args) throws ExecutionException, InterruptedException {
DeptService deptService = new DeptService();
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
Dept dept = deptService.getById(1);
return dept;
})
.thenRun(() -> {//注意没有入参
System.out.println("线程:" + Thread.currentThread().getName() + " do something");
//thenRun注意没有入参,也没有返回值
});
System.out.println("线程:" + Thread.currentThread().getName() +
" 结果:" + voidCompletableFuture.get());
}
}
运行结果如下:
线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:main do something
线程:main 结果:null
4. thenCompose
thenCompose 可以用于组合多个CompletableFuture,将前一个任务的返回结果作为下一个任务的参数,它们之间存在着先后顺序。
thenCompose方法会在某个任务执行完成后,将该任务的执行结果作为方法入参然后执行指定的方法,该方法会返回一个新的CompletableFuture实例,如果该CompletableFuture实例的result不为null,则返回一个基于该result的新的CompletableFuture实例;如果该CompletableFuture实例为null,则,然后执行这个新任务。
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) ;
thenApply()和thenCompose()的区别:
thenApply()转换的是泛型中的类型,是同一个CompletableFuture,相当于将CompletableFuture 转换成CompletableFuture;
thenCompose()用来连接两个CompletableFuture,是生成一个新的CompletableFuture。
下面对thenApply的测试代码进行修改,注意下图中区别
- thenCompose生成了一个新的CompletableFuture
- thenApply还是原来的CompletableFuture,只是泛型从Dept转换成User。
public class Thread06_SupplyAsync_ThenCompose {
public static void main(String[] args) throws ExecutionException, InterruptedException {
DeptService deptService = new DeptService();
UserService userService = new UserService();
User user = new User(1, "冬哥", 31);
CompletableFuture<User> userCompletableFuture = CompletableFuture.supplyAsync(() -> {
Dept dept = deptService.getById(1);
return dept;
})
.thenCompose(dept -> CompletableFuture.supplyAsync(() -> {
//注意这里用到了上个线程的返回值dept
user.setDeptId(dept.getId());
user.setDeptName(dept.getName());
return userService.save(user);
}));
System.out.println("线程:" + Thread.currentThread().getName() +
" 结果:" + userCompletableFuture.get().toString());
}
}
运行结果如下:
线程:ForkJoinPool.commonPool-worker-1 getById(1)
线程:ForkJoinPool.commonPool-worker-1 save(),User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
线程:main 结果:User{id=1, name='冬哥', age=31, DeptId=1, DeptName='研发一部'}
7. thenApply,thenAccept,thenRun,thenCompose的区别
thenApply,thenAccept,thenRun,thenCompose的区别如下:
特点 | thenApply | thenAccept | thenRun | thenCompose |
---|---|---|---|---|
入参 | 有 | 有 | 无 | 有 |
返回值 | 有 | 无 | 无 | 有 |
新的CompletableFuture | 无 | 无 | 无 | 有 |
8. 组合方法
allOf | anyOf |
---|---|
等待所有任务完成,构造后CompletableFuture完成 | 只要有一个任务完成,构造后CompletableFuture就完成 |
/**
* 使用定制的 Executor 配置 CompletableFuture
*
* @param product
* @return
*/
public List<String> findPrice4(String product) {
//为“最优价格查询器”应用定制的执行器 Execotor
Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(), 100),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//使用守护线程,使用这种方式不会组织程序的关停
thread.setDaemon(true);
return thread;
}
}
);
//将执行器Execotor 作为第二个参数传递给 supplyAsync 工厂方法
List<CompletableFuture<String>> futures = shops.stream()
.map(shop -> CompletableFuture.supplyAsync(
() -> String.format("%s price is %.2f RMB",
shop.getName(),
shop.getPrice(product)), executor)
)
.collect(toList());
List<String> list = futures.stream()
.map(CompletableFuture::join)
.collect(toList());
return list;
}