java中的异步机制

在最近的项目中,前台出现了一次500的异常,错误信息是 read time out .也就是说,调用的后台方法执行时间过长,然后在前台等待的时间里,没有方法没有执行结束,没有相应的相应,所以这时候,前后台的连接就断开了.而此次我解决问题选择的方法是在后台使用异步机制.

为什么要使用异步

简单来说,使用异步出于无奈.在同一个线程中,一个函数执行的时间过长,那么后面的函数就只能等着,那么我们就需要另外使用一个线程,将运行时间长的函数放到这个单独的线程中去执行.

java 异步编程

下面介绍两种 java 中的异步编程方式.

Future方式的异步编程

Future 表示未来的某个异步计算的结果, 当我们向线程池提交任务的时候,就会获得这个对象. 然后可以使用 get 方法来获取计算完成的结果,并且只有在计算完成时才能使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface ArchiveSearcher { String search(String target); }
class App {
ExecutorService executor = ... // 执行者,用来执行函数
ArchiveSearcher searcher = ...
void showSearch(final String target) throws InterruptedException {
Future<String> future = executor.submit(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
displayOtherThings(); // 在这里可以做一些其他的事情
try {
displayText(future.get()); // 使用 future, 获取结果
} catch (ExecutionException ex) {
cleanup();
return;
}
}
}

可以看到,在这段代码中,我们先定义了一个执行者,他会执行未来的某个函数.然后我们定义了一个Future对象,他的值就是上面的执行者 executor 执行了 call 函数后的返回值.这个call函数中就是我们要执行的运行时间很长的函数.在他之后,这个函数或交给另一个线程去执行,而我们也不用等待这个函数执行完,可以去做一些其他的事情.最后,使用 Future 对象中的get方法,获取函数执行后的返回值.

注意: 这里有可能会抛出一个执行异常,所以我们需要在这里使用try catch来处理一下异常.

CompletableFuture方式的异步编程

首先肯定就有人会问,有了Future,那为什么还要选择CompletableFuture这种异步编程的方式呢?

答案显而易见:Future存在缺陷.

Future虽然实现了异步,但是他缺少通知机制,所以我们并不能知道函数什么时候执行结束.

使用阻塞,在future.get()的地方等待函数返回结果,但是这样就又成了同步的方式了.

使用isDone来进行轮询判断Future是否执行结束,但这样又会显得很多余,浪费很多的CPU.

有问题必然就要解决,所以就有了我们的 CompletableFuture 的编程方式.

CompletableFuture 这种异步编程的方式是在java8才出现的新的异步编程方式.他解决了一些在Future上存在的问题.使得java完整的实现了非阻塞方式的异步编程.

1
2
3
4
5
6
7
8
9
10
11
12
13
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Hello");
});
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("CompletableFuture");

这里我们可以看到,我们调用了CompletableFuture中的一个静态方法runAsync()来执行异步代码(这里会默认分配给他一个线程池), 他会返回一个没有返回值的CompletableFuture.

当然, CompletableFuture 当中还有很多的静态方法,可以获取没有返回值的,同样也可以获取有返回值的.这里就不一一介绍了.在最后我会给出链接,读者可自行学习.

总结

虽然我们有异步编程的方式去解决函数执行时间长的问题,但是却不要首先想到这个执行办法,首先还是要看我们的函数时候能够减少数据库的IO操作,然后再去想是否应该使用异步编程.

官方参考:

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html

坚持原创技术分享,您的支持将鼓励我继续创作!