@Async()注解快速实现
@Async注解就能简单的将原来的同步函数变为异步函数;
- 在你需要异步的方法上使用@Async注解;@Async()注解可以写上线程池名称,这样就会指定使用的线程池,当然需要我们自定义线程池,这里我写上不同的线程池,最后可以看结果;
/**
* fshows.com
* Copyright (C) 2013-2019 All Rights Reserved.
*/
package com.example.thread.demo.service.impl;
import com.example.thread.demo.service.ThreadService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* @author liuyuan
* @version ThreadServiceImpl.java, v 0.1 2019-11-01 11:06
*/
@Service
public class ThreadServiceImpl implements ThreadService {
@Override
// 线程池1
@Async("taskExecutor1")
public void doTaskA() throws InterruptedException {
System.out.println("TaskA thread name->" + Thread.currentThread().getName());
Long startTime = System.currentTimeMillis();
TimeUnit.SECONDS.sleep(2);
Long endTime = System.currentTimeMillis();
System.out.println("TaskA 耗时:" + (endTime - startTime));
}
@Override
// 线程池2
@Async("taskExecutor2")
public void doTaskB() throws InterruptedException {
System.out.println("TaskB thread name->" + Thread.currentThread().getName());
Long startTime = System.currentTimeMillis();
TimeUnit.SECONDS.sleep(2);
Long endTime = System.currentTimeMillis();
System.out.println("TaskB耗时:" + (endTime - startTime));
}
}
- 为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync注解;
package com.example.thread.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* @author liuyuan
*/
@SpringBootApplication
@EnableAsync
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3.自定义线程池;当然可以不用定义线程池,会使用自带的线程池,但是通过线程池控制可以更好的控制并发;我这里用了三种线程池;
/**
* fshows.com
* Copyright (C) 2013-2019 All Rights Reserved.
*/
package com.example.thread.demo.config;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池配置
*
* @author liuyuan
* @version ThreadPoolTaskConfig.java, v 0.1 2019-11-01 11:10
*/
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
/**
* 核心线程数(默认线程数)
*/
private static final int CORE_POOL_SIZE = 10;
/**
* 最大线程数
*/
private static final int MAX_POOL_SIZE = 100;
/**
* 允许线程空闲时间(单位:默认为秒)
*/
private static final int KEEP_ALIVE_TIME = 10;
/**
* 缓冲队列数
*/
private static final int QUEUE_CAPACITY = 200;
/**
* 线程池名前缀
*/
private static final String THREAD_NAME_PREFIX = "Async-Service-";
/**
* bean的名称,默认为首字母小写的方法名
*/
// spring管理的线程池,顶级父类也是Executor
@Bean("taskExecutor1")
public ThreadPoolTaskExecutor taskExecutor1() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
// 线程池对拒绝任务的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
executor.initialize();
return executor;
}
/*************************************************分割线*************************************************/
private static final int THREADS = Runtime.getRuntime().availableProcessors() + 1;
private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
// -%d不要少
.setNameFormat("async-task-name-%d")
.setDaemon(true)
.build();
// JDK原生线程池,建议返回值为ExecutorService,可以调用submit()方法启动线程
@Bean("taskExecutor2")
public Executor taskExecutor2() {
return new ThreadPoolExecutor(THREADS, 2 * THREADS,
5, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1024),
threadFactory, (r, executor) -> {
// 打印日志,添加监控等
System.out.println("task is rejected!");
});
}
/*****************************************************分割线**************************************************/
// NamedThreadFactory需要自己定义
@Bean("taskExecutor3")
public Executor taskExecutor3() {
return new ThreadPoolExecutor(2, 50, 60L,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2048),
new NamedThreadFactory("api-apply-thread"));
}
}
- 运行结果,service层自行补齐;
/**
* fshows.com
* Copyright (C) 2013-2019 All Rights Reserved.
*/
package com.example.thread.demo.controller;
import com.example.thread.demo.service.ThreadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
1. @author liuyuan
2. @version ThreadDemo01.java, v 0.1 2019-11-01 11:02
*/
@RestController
@RequestMapping("/test")
public class ThreadDemo01 {
@Autowired
private ThreadService threadService;
@GetMapping("/async")
public String testAsync() throws Exception {
System.out.println("主线程开始 name -->" + Thread.currentThread().getName());
threadService.doTaskA();
threadService.doTaskB();
System.out.println("主线程结束 name -->" + Thread.currentThread().getName());
return "Hello World";
}
}
结果:主线程、TaskA和TaskB线程名都不相同,说明启用了不同的线程;
- 注意事项,以下情况会使@Async失效;
- 异步方法使用static修饰 ;
- 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类;
- 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象;
- 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解 ;
- 在Async方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效;
Future、FutureTask介绍
Future是一个接口,该接口用来返回异步的结果,FutureTask是一个类,是Future 的一个实现。
- Future接口介绍;
public interface Future<V> {
// 取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束;返回值指是否取消成功;
boolean cancel(boolean mayInterruptIfRunning);
// 任务是否已经取消,任务正常完成前将其取消,则返回 true;
boolean isCancelled();
// 任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true;
boolean isDone();
// 等待任务执行结束,然后获得V类型的结果,若无结果会阻塞至异步计算完成。
// InterruptedException 线程被中断异常, ExecutionException 任务执行异常,CancellationException 任务取消异常;
V get() throws InterruptedException, ExecutionException;
// 同上面的get功能一样,多了设置超时时间。
// 参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计 算超时,将抛出TimeoutException
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
- 使用方法;
一般情况下,我们会结合Callable和Future一起使用,通过ExecutorService的submit方法执行Callable,并返回Future。
@Override
public void doTaskE() {
ThreadPoolTaskExecutor pool = threadPoolTaskConfig.taskExecutor1();
// Lambda 是一个 callable, 提交后便立即执行,这里返回的是 FutureTask 实例
Future<String> future = pool.submit(() -> {
System.out.println("running task");
Thread.sleep(1000);
return "return task";
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
//前面的的 Callable 在其他线程中运行着,可以做一些其他的事情
System.out.println("do something else");
//等待 future 的执行结果,执行完毕之后打印出来
try {
System.out.println(future.get());
} catch (InterruptedException e) {
} catch (ExecutionException e) {
}
}
- 返回值;
使用Future,返回异步的结果
还是和之前一样,我们使用@Async注解快速实现;
- 实现类代码,返回值自己定义,我这边定义一个map;
@Override
@Async("taskExecutor3")
public Future<Map<String,String>> doTaskC() throws InterruptedException {
System.out.println("TaskC thread name->" + Thread.currentThread().getName());
Long startTime = System.currentTimeMillis();
TimeUnit.SECONDS.sleep(2);
Long endTime = System.currentTimeMillis();
System.out.println("TaskC耗时:" + (endTime - startTime));
Map<String, String> map = Maps.newHashMap();
map.put("return","返回值是一个map!");
return new AsyncResult<>(map);
}
- 获取结果;
@GetMapping("/async")
public String testAsync() throws Exception {
System.out.println("主线程开始 name -->" + Thread.currentThread().getName());
// threadService.doTaskA();
// threadService.doTaskB();
Future<Map<String, String>> future = threadService.doTaskC();
while (!future.isDone()) {
System.out.println("Wait asyncTaskWithResult.");
Thread.sleep(1000);
}
System.out.println("asyncTaskWithResult result is:" + future.get().toString());
System.out.println("主线程结束 name -->" + Thread.currentThread().getName());
return "Hello World";
}
- 结果;
使用FutureTask,返回异步的结果
采用普通的方式,开启线程;
- 实现类;
@Override
public FutureTask<String> doTaskD() {
Long startTime = System.currentTimeMillis();
FutureTask<String> future = new FutureTask<>(() -> {
TimeUnit.SECONDS.sleep(2);
Long endTime = System.currentTimeMillis();
System.out.println("TaskC耗时:" + (endTime - startTime));
return "成功获取future异步任务结果,threadName = " + Thread.currentThread().getName();
});
Executor pool = threadPoolTaskConfig.taskExecutor3();
pool.execute(future);
return future;
}
- 获取结果;
@GetMapping("/async")
public String testAsync() throws Exception {
System.out.println("主线程开始 name -->" + Thread.currentThread().getName());
// threadService.doTaskA();
// threadService.doTaskB();
// Future<Map<String, String>> future = threadService.doTaskC();
FutureTask<String> future = threadService.doTaskD();
boolean flag = true;
while (flag) {
//异步任务完成并且未被取消,则获取返回的结果
if (future.isDone() && !future.isCancelled()) {
System.out.println("asyncTaskWithResult result is:" + future.get());
flag = false;
}
}
System.out.println("主线程结束 name -->" + Thread.currentThread().getName());
return "Hello World";
}
- 结果;
CompletableFuture和CompletionStage介绍
- Future模式的缺点;
- Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知机制,我们无法得知Future什么时候完成;
- Future是Java 5添加的类,用来描述一个异步计算的结果,但是获取一个结果时方法较少,要么通过轮询isDone,确认完成后,调用get()获取值,要么调用get()设置一个超时时间。但是这个get()方法会阻塞住调用线程,这种阻塞的方式显然和我们的异步编程的初衷相违背。
- CompletableFuture介绍;
-
CompletableFuture能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。它避免了传统回调最大的问题,那就是能够将控制流分离到不同的事件处理器中。
-
CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。
- CompletableFuture的方法介绍;
- CompletableFuture的静态工厂方法
runAsync 和 supplyAsync 方法的区别是runAsync没有返回值;
- CompletionStage介绍;
CompletionStage是一个接口,从命名上看得知是一个完成的阶段,它里面的方法也标明是在某个运行阶段得到了结果之后要做的事情。
- 进行变换
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);
public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn,Executor executor);
首先说明一下已Async结尾的方法都是可以异步执行的,如果指定了线程池,会在指定的线程池中执行,如果没有指定,默认会在ForkJoinPool.commonPool()中执行,下文中将会有好多类似的,都不详细解释了。关键的入参只有一个Function,它是函数式接口,所以使用Lambda表示起来会更加优雅。它的入参是上一个阶段计算后的结果,返回值是经过转化后结果。
例如:
@Override
public void doTaskG() {
String result = CompletableFuture.supplyAsync(() -> {
System.out.println("线程1" + Thread.currentThread().getName());
return "hello";
}, threadPoolTaskConfig.taskExecutor1()).thenApplyAsync(s -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2" + Thread.currentThread().getName());
return s + "dosomething";
}, threadPoolTaskConfig.taskExecutor2()).join();
System.out.println("return: " + result);
}
public String testAsync() throws Exception {
System.out.println("主线程开始 name -->" + Thread.currentThread().getName());
threadService.doTaskG();
System.out.println("主线程结束 name -->" + Thread.currentThread().getName());
return "Hello World";
}
结果:
分析:可以看到,我使用的是异步方法,并且结果也开启了了两个线程,但是获取返回值还是阻塞了主线程的执行;
- 进行消耗
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);
thenAccept是针对结果进行消耗,因为他的入参是Consumer,有入参无返回值。
例如:
@Override
public void doTaskH() {
CompletableFuture.supplyAsync(() -> {
System.out.println("线程1" + Thread.currentThread().getName());
return "CompletableFuture";
}, threadPoolTaskConfig.taskExecutor1()).thenAcceptAsync(s -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2" + Thread.currentThread().getName());
System.out.println("return: " + s + "===" + "thenAcceptAsync");
}, threadPoolTaskConfig.taskExecutor2());
}
public String testAsync() throws Exception {
System.out.println("主线程开始 name -->" + Thread.currentThread().getName());
threadService.doTaskH();
System.out.println("主线程结束 name -->" + Thread.currentThread().getName());
return "Hello World";
}
结果:
分析:执行supplyAsync()和thenAcceptAsync()时分别开了不同的线程池,并且返回值没有阻塞主线程,也就是说实现了异步处理的结果并且将结果交给另外一个异步事件处理线程来处理。
注意:在函数结尾千万不要加.join()方法,否则还是会阻塞处理返回值;