我们讲述几种比较实用的,平时写代码可能用到的方法
1 以下这几种实际写代码不会用到
1.1继承Thread类
1.2 实现Runable接口
1.3 实现Callable 接口
上述三种都是使用 接口或父类中的 start() 方法来实现接口的异步执行, 不过平时写代码几乎不会用到, 因为太原始了 ,只有面试的时候可能才用得到.
2 使用springboot中的@Async注解
@Async注解实际上采用的是动态代理进行异步实现, 所以该注解需要放在spring管理的Bean上.
2.1 直接使用
将注解直接标注在spring管理的类中的方法上
@Async()
@Override
public void sout1() {
try {
Thread.sleep(3000);
log.info(Thread.currentThread().getName());
System.out.println("当前时间为:" + simpleDateFormat.format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.2 指定线程池
如下方法指定了线程池 sout2
@Async("sout2")
@Override
public void sout2() {
try {
Thread.sleep(3000);
log.info(Thread.currentThread().getName());
System.out.println("当前时间为:" + dateTimeFormatter.format(LocalDateTime.now()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
线程池可以按照如下配置
@Configuration
@EnableAsync
public class ExecutorConfig {
/**
* 线程池维护线程的最少数量
*/
private final int corePoolSize = 1;
/**
* 线程池维护线程的最大数量
*/
private final int maxPoolSize = 1;
/**
* 缓存队列
*/
private final int queueCapacity = 1;
/**
* 允许的空闲时间,单位秒(S)
*/
private final int keepAlive = 30;
@Bean("sout2")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("boot-executor-sout2-");
executor.setKeepAliveSeconds(keepAlive);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
2.3 有返回值的使用
可以使用 Future接收异步线程的返回值, 这表示你需要该异步线程执行完毕后的结果
get() 方法, 总线程会陷入阻塞,只有等到使用get()方法的线程执行完毕才会继续进行.
也就是说即使是你的异步接口有返回值,但是你并没有调用 get()方法,主线程并不会阻塞,会按照原步骤继续执行.
@RequestMapping(value = "/ex3", method = RequestMethod.GET)
public Object sout3(HttpServletRequest request, HttpServletResponse response) throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
Future<String> String3 = testService.sout3();
Future<String> String4 = testService.sout4();
Future<String> String5 = testService.sout5();
System.out.println(" 执行完毕ex3");
String3.get();
String4.get();
String5.get();
//logger.info("111:{}---222:{}---333:{}",String3.get(),String4.get(),String5.get());
logger.info("执行时间为:{}",(System.currentTimeMillis()-start)/1000);
return null;
}
/*******/
......
/*******/
@Override
@Async("sout2")
public Future<String> sout3() {
try {
Thread.sleep(3000);
//log.info("sout3方法执行1======>>>{}",Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
return AsyncResult.forValue("sout3");
}
2.4注意事项
因为使用了动态代理, 所以你在同一个类中调用并不会异步执行,所以不可以放在同一个类中
异步方法不允许使用static 修饰
@Async不可以与 @Transactional 注解出现在同一个类中
3 使用Future
CompletableFuture 是 java.util.concurrent包中提供的类
CompletableFuture<T> aFuture = CompletableFuture.supplyAsync(() -> {
return T;
}, threadPool);
// 以下方法会使用容器自带的线程池
//CompletableFuture<T> aFuture = CompletableFuture.supplyAsync(() -> {
// return T;
//});
// CompletableFuture.supplyAsync(() -> {
// return T;
//}, threadPool).get();
CompletableFuture allFuture = CompletableFuture.allOf(aFuture, bFuture, vFuture, propertyFuture, dFuture,eFuture, fFuture);
allFuture.get();
当CompletableFuture全部线程执行完毕后, 主线程才会继续执行.
4 使用 ListeningExecutorService
引入依赖
<!--guava异步-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.2-jre</version>
</dependency>
// 可以使用自己配置的线程池 上面有配置示例
// @autowired
// private ThreadPoolTaskExecutor threadPoolTaskExecutor
// 也可以自己new 缓存线程
ExecutorService threadPool = Executors.newCachedThreadPool();
ListeningExecutorService service = MoreExecutors.listeningDecorator(threadPool);
ListenableFuture<T> t= service.submit(() -> { return T;});
t.get();
get() 方法实现的都是一样的, 将主线程阻塞,等待异步线程执行完毕.