线程池-工作使用

107 阅读4分钟

背景

之前讲了多线程和线程池的理论,现在来讲下应用场景。

这里主要讲3个应用场景:
1、发mq消息
2、多数据源查询数据
3、接口调用

发mq消息

比如说,支付成功之后,会更新订单状态为成功,并且通知商户。

怎么通知?基于mq。

怎么提高发消息的性能?虽然mq本身是异步,解决解耦的问题,但是呢,发送的时候也可以提高性能,怎么提高?还是异步,异步的本质就是多线程,多线程的本质就是线程池,所以具体的话说白了,就是基于线程池发mq消息,这个是代码层面的提高性能。

具体使用的线程池类,当然是ThreadPoolExecutor。

而且,发消息的子线程任务,主线程也不需要知道结果。所以,就是线程池的简单使用即可。

多数据源查询数据

需求背景是这样的,就是有多个数据库,然后呢,查询的时候,数据不知道在哪个库,那怎么办呢?只能都查。

都查没有问题,但是怎么提高性能呢?因为都查的话,相当于要花两倍的时间。

解决方法也是线程池,并发查询多个数据库,然后再get数据。


这里其实有两个解决方案,一个是基于Callable和Future,一个是基于CompletableFuture。

但是最佳实践是基于CompletableFuture。

为什么呢?因为CompletableFuture的作用就是为了解决这个问题,即并发查询的问题,而且还不阻塞。

而基于Callable和Future的方案,其实是会阻塞的,虽然并发查询的时候,不会阻塞,但是get数据的时候,会阻塞。即如果A线程get数据阻塞等待(因为A线程可能还没有执行完成),那么B线程也get不了数据,因为B线程读数据的代码还没执行呢。

所以,两个方案的区别在于,一个是解决并发查询的问题,一个是解决并发查询 + 并发get数据。说白了,就是只要有一个成功,就立即返回。

接口调用

背景

支付的时候,交易链路很长,依赖了很多外部服务,正常情况下,就是执行到哪个接口,就调用哪个接口,现在要提高性能,怎么提高?还是得异步。

说白了,就是提前调用接口,把调用接口直接放到入口的地方,并且是同时发起多个接口的调用,等到用的时候,即代码执行到真正调用接口的地方,直接get数据即可,省掉了接口调用的时间。

一个接口就省掉了很多时间,那么多个接口,就很可观了。

并发执行调用多个接口,每个接口的结果是Future。那怎么把接口和对应的Future关联起来?基于map<接口名字,Future>。为什么要关联起来?因为现在是提前调用,提前写,读的话是在后面,所以要暂时存储起来,怎么存储?使用map即可。

除了需要关联哪个接口的返回结果,还需要关联哪个线程的结果。说白了,就是当前请求进来的线程,在入口写数据之后,后面读的时候,仍然是当前这个请求线程的数据,而不是和其他请求线程混淆了。解决方法?基于ThreadLocal<Map<接口名字,Future>>。

说白了,就是要解决两个问题:
1、哪个接口的Future
2、哪个请求线程的map

因为一个请求线程进来之后,会调用多个接口,所以使用map。

另外,一个服务,同一时间,是有多个请求线程进来的,所以使用ThreadLocal。为什么ThreadLocal可以解决关联当前线程和数据的问题?因为ThreadLocal的底层也是基于map,说白了就是map<线程id,线程数据>,即一个ThreadLocal对象存储了多个线程的数据,然后哪个线程写和读的时候,操作的都是当前线程的数据。

讲完了写,读就很简单了,入口提前写数据,读数据就是使用的时候就读数据,其实就是代码执行到真正调用接口的地方,然后直接get数据即可。

具体的伪代码就是:先ThreadLocal.get(),得到map<接口名字,Future>。然后再map.get(接口名字),得到Future。最后才是Future.get(),得到数据。

小结

核心就两点:
1、基于线程池提前并发执行,提高性能
2、基于ThreadLocal传递线程数据,因为写数据和读数据有时间差

其他应用场景

除了提前并发执行不同接口,还有一种情况是,同一个接口,在交易链路可能执行多次,那怎么提高性能?在第一次执行的时候,get数据之后,先缓存起来map<商家号,商家信息>,map还要放入ThreadLocal,即ThreadLocal<Map<商家号,商家信息>>,因为后面还要多次使用,使用的时候,直接从当前线程的map读数据即可,入参就是商户号,就不需要再次查询接口了。