异步的方式
异步用在同一个线程中的场景也有,但是我遇到的不多,我遇到的更多的是把异步用在不同的线程中。
现实开发中我使用到异步的场景一般都会牵涉到两个线程,在主线程中有它主要的执行逻辑,然后在副线程中执行异步操作。有下面几个场景:
1.提高主线程的运行速率,把耗时操作放到另外一个线程异步执行
使用异步的第一个开发场景就是,有时候我们如果只用一个主线程执行速度太慢了,需要大量的等待耗时操作,很耗费时间,所以这个时候就可以多引入一个线程,把主线程中耗时的操作单独执行,这样在执行耗时操作的同时,我们可以异步的执行主线程中的其他逻辑,可以大大提高程序运行时间。
比如现在我们有个需求场景,就是需要读取数据库里面的数据,这个过程非常耗时,比如我们需要读取Student学生表的大量的数据;然后还有个逻辑是,我们要进行100个数值计算,但是关于这100个数值计算与Student数据库表没有任何关系;第三步是让每行学生信息中的总分加上我们的100个数值计算的值;
那么现在我们分析一下上面的场景,耗时比较长的操作是什么?是从磁盘中读取数据库表数据到内存中;那么我们能不能把这一部分耗时的操作单独的抽取出来执行呢?
现在想一下假如我们只用一个主线程执行这段逻辑,那么这三部分我就必须要按照顺序执行,并且前面的部分还没有执行完毕的时候,后面的部分是不会被执行的。那假如说我们第一部分读取数据库表的操作非常的耗时,这个时候我们的整个程序运行的时间就会非常的长。那么能不能让程序执行的快一些呢?可以的,用什么办法呢?我们可以把执行时间比较长的操作,也就是读取数据库的操作放到另外一个线程中异步执行。相当于是cpu并发执行了两个线程,那有人可能会有这样的疑问,假如我现在cpu调度了副线程去读取mysql数据到磁盘中,假如这个时候切换线程,去执行主线程的其他的逻辑,比如计算100个数值,那这个时候我们仍然没有执行mysql读取的操作啊?后面仍然是要切换线程到副线程去读取mysql数据库的数据啊?程序总的消耗时间是一样的啊?其实我刚开始也有这样的顾虑,那是因为他们可能和我一样弄错了一个概念,就是当cpu调度副线程读取数据库数据,也就是把磁盘数据加载到内存中的时候,既变这个时候cpu再去调度其他的线程,我们的磁盘数据加载到内存也会一直在执行。这就像是有一个装满水的带有水塞的水槽,当你的大脑给手一个命令打开水塞的时候,当你打开水塞之后,水会一直从水槽往外流,既便这个时候你的大脑给脚一个跑的命令,水槽里的水仍然是在向外流的一样。
因此两个线程在某些情况下一定是比一个线程执行效率要高的。
那再次回到我们上面的例子中,怎么能让程序执行的快一些呢?我们就可以创建两个线程,把读取数据库的耗时操作单独的放到副线程中异步执行,在主线程中执行其他逻辑,最后当副线程异步执行的结果出来之后给主线程就可以,代码如下:
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10); // 创建线程池
Future<Integer> future = executor.submit(() -> {
// 模拟读取数据库耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
System.out.println("Main thread continues to work...");
//第一个数值计算
int result1 = method1();
//第二个数值计算
int result2 = method2();
//第三......第100
int result100 = method100();
try {
int baseDataValue = future.get(); // 等待异步操作完成并获取结果
//获取异步结果之后,把异步结果分别和我们的数值计算结果相加
baseDataValue + result1 .....
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown(); // 关闭线程池
}
}
上面的代码就可以大大提高我们程序的整体的运行效率,因为我们在执行数据库读取这个耗时操作的时候,是放到了另外一个副线程中执行的,在它读取数据的同时,我们仍然可以处理主线程中与数据库无关的操作,就是上面的一百个数值计算的操作,最后必须要用到数据库结果的时候再通过future.get方法获取到副线程中异步执行的结果就好了。
2.跨服获取数据,也可以用异步,不过使用的是CompletableFuture异步执行关键字
比如现在有两个服,一个server服,一个scene服,在scene服中想要异步获取到server服的数据,就可以使用CompletableFuture关键字。
代码如下:
//下面是在scene服的代码
class Test {
public static void main(String[] args) {
//查询参数
int param = 11111;
//执行主线程中的其他逻辑
其他逻辑代码执行.....
CompletableFuture<String> future = new CompletableFuture<>();
//使用mq消息中间件给server服发送一个notice通知,告诉server服查询参数
sendNotice(param);
//得到从server服通过notice通知发送过来的数据
int result = getDataFromNotice();
//完成CompletableFuture异步
future.complete(result);
//主线程得到异步线程中的异步执行结果,从创建CompletableFuture到完成异步数据获取,这个时间不能超过1000ms,否则会主动抛出异常
future.get(1000, TimeUnit.MILLISECONDS);
}
}
//下面是在server服的代码
class ServerTest {
public static void main(String[] args) {
//reviceNotice
int param = getParamFromScene();
//根据参数得到server中的需要返回给scene服的数据
int result = getDataByParam(param);
//把数据返回给scene服
sendNoticeToScene(result);
}
}
通过上图的代码,可以借助mq消息通知和CompletableFuture异步关键字跨服的访问另外一个服的数据,并且可以规定异步获取数据的最大时间,超过这个时间就会抛出异常。