异步转同步

业务需求

有些接口查询反馈结果是异步返回的,无法立刻获取查询结果。

  • 正常处理逻辑

触发异步操作,然后传递一个唯一标识。

等到异步结果返回,根据传入的唯一标识,匹配此次结果。

  • 如何转换为同步

正常的应用场景很多,但是有时候不想做数据存储,只是想简单获取调用结果。

即想达到同步操作的结果,怎么办呢?

思路

  1. 发起异步操作

  2. 在异步结果返回之前,一直等待(可以设置超时)

  3. 结果返回之后,异步操作结果统一返回

循环等待

  • LoopQuery.java

使用 query(),将异步的操作 remoteCallback() 执行完成后,同步返回。

public class LoopQuery implements Async {

    private String result;

    private static final Logger LOGGER = LogManager.getLogger(LoopQuery.class.getName());

    @Override
public String query(String key) {
startQuery(key);
new Thread(new Runnable() {
@Override
public void run() {
remoteCallback(key);
}
}).start(); final String queryResult = endQuery();
LOGGER.info("查询结果: {}", queryResult);
return queryResult;
} /**
* 开始查询
* @param key 查询条件
*/
private void startQuery(final String key) {
LOGGER.info("执行查询: {}", key);
} /**
* 远程的回调是等待是随机的
*
* @param key 查询条件
*/
private void remoteCallback(final String key) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.result = key + "-result";
LOGGER.info("remoteCallback set result: {}", result);
} /**
* 结束查询
* @return 返回结果
*/
private String endQuery() {
while (true) {
if (null == result) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
return result;
}
}
}
}
  • main()
public static void main(String[] args) {
new LoopQuery().query("12345");
}
  • 测试结果
18:14:16.491 [main] INFO  com.github.houbb.thread.learn.aysnc.loop.LoopQuery - 执行查询: 12345
18:14:21.498 [Thread-1] INFO com.github.houbb.thread.learn.aysnc.loop.LoopQuery - remoteCallback set result: 12345-result
18:14:21.548 [main] INFO com.github.houbb.thread.learn.aysnc.loop.LoopQuery - 查询结果: 12345-result

CountDownLatch

  • AsyncQuery.java

使用 CountDownLatch 类达到同步的效果。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; public class AsyncQuery { private static final Logger LOGGER = LogManager.getLogger(AsyncQuery.class.getName()); /**
* 结果
*/
private String result; /**
* 异步转同步查询
* @param key
*/
public void asyncQuery(final String key) {
final CountDownLatch latch = new CountDownLatch(1);
this.startQuery(key); new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("远程回调线程开始");
remoteCallback(key, latch);
LOGGER.info("远程回调线程结束");
}
}).start(); try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} this.endQuery();
} private void startQuery(final String key) {
LOGGER.info("执行查询: {}", key);
} /**
* 远程的回调是等待是随机的
* @param key
*/
private void remoteCallback(final String key, CountDownLatch latch) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.result = key + "-result";
latch.countDown();
} private void endQuery() {
LOGGER.info("查询结果: {}", result);
} }
  • main()
public static void main(String[] args) {
AsyncQuery asyncQuery = new AsyncQuery();
final String key = "123456";
asyncQuery.asyncQuery(key);
}
  • 日志
18:19:12.714 [main] INFO  com.github.houbb.thread.learn.aysnc.countdownlatch.AsyncQuery - 执行查询: 123456
18:19:12.716 [Thread-1] INFO com.github.houbb.thread.learn.aysnc.countdownlatch.AsyncQuery - 远程回调线程开始
18:19:17.720 [main] INFO com.github.houbb.thread.learn.aysnc.countdownlatch.AsyncQuery - 查询结果: 123456-result
18:19:17.720 [Thread-1] INFO com.github.houbb.thread.learn.aysnc.countdownlatch.AsyncQuery - 远程回调线程结束

Spring EventListener

使用观察者模式也可以。(对方案一的优化)

此处结合 spring 进行使用。

  • BookingCreatedEvent.java

定义一个传输属性的对象。

public class BookingCreatedEvent extends ApplicationEvent {

    private static final long serialVersionUID = -1387078212317348344L;

    private String info;

    public BookingCreatedEvent(Object source) {
super(source);
} public BookingCreatedEvent(Object source, String info) {
super(source);
this.info = info;
} public String getInfo() {
return info;
}
}
  • BookingService.java

说明:当 this.context.publishEvent(bookingCreatedEvent); 触发时,

会被 @EventListener 指定的方法监听到。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service
public class BookingService { @Autowired
private ApplicationContext context; private volatile BookingCreatedEvent bookingCreatedEvent; /**
* 异步转同步查询
* @param info
* @return
*/
public String asyncQuery(final String info) {
query(info); new Thread(new Runnable() {
@Override
public void run() {
remoteCallback(info);
}
}).start(); while(bookingCreatedEvent == null) {
//.. 空循环
// 短暂等待。
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
//...
}
//2. 使用两个单独的 event... } final String result = bookingCreatedEvent.getInfo();
bookingCreatedEvent = null;
return result;
} @EventListener
public void onApplicationEvent(BookingCreatedEvent bookingCreatedEvent) {
System.out.println("监听到远程的信息: " + bookingCreatedEvent.getInfo());
this.bookingCreatedEvent = bookingCreatedEvent;
System.out.println("监听到远程消息后: " + this.bookingCreatedEvent.getInfo());
} /**
* 执行查询
* @param info
*/
public void query(final String info) {
System.out.println("开始查询: " + info);
} /**
* 远程回调
* @param info
*/
public void remoteCallback(final String info) {
System.out.println("远程回调开始: " + info); try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} // 重发结果事件
String result = info + "-result";
BookingCreatedEvent bookingCreatedEvent = new BookingCreatedEvent(this, result);
//触发event
this.context.publishEvent(bookingCreatedEvent);
}
}
  • 测试方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest { @Autowired
private BookingService bookingService; @Test
public void asyncQueryTest() {
bookingService.asyncQuery("1234");
} }
  • 日志
2018-08-10 18:27:05.958  INFO  [main] com.github.houbb.spring.lean.core.ioc.event.BookingService:84 - 开始查询:1234
2018-08-10 18:27:05.959 INFO [Thread-2] com.github.houbb.spring.lean.core.ioc.event.BookingService:93 - 远程回调开始:1234
接收到信息: 1234-result
2018-08-10 18:27:07.964 INFO [Thread-2] com.github.houbb.spring.lean.core.ioc.event.BookingService:73 - 监听到远程的信息: 1234-result
2018-08-10 18:27:07.964 INFO [Thread-2] com.github.houbb.spring.lean.core.ioc.event.BookingService:75 - 监听到远程消息后: 1234-result
2018-08-10 18:27:07.964 INFO [Thread-2] com.github.houbb.spring.lean.core.ioc.event.BookingService:106 - 已经触发event
2018-08-10 18:27:07.964 INFO [main] com.github.houbb.spring.lean.core.ioc.event.BookingService:67 - 查询结果: 1234-result
2018-08-10 18:27:07.968 INFO [Thread-1] org.springframework.context.support.GenericApplicationContext:993 - Closing org.springframework.context.support.GenericApplicationContext@5cee5251: startup date [Fri Aug 10 18:27:05 CST 2018]; root of context hierarchy

超时和空循环

空循环

空循环会导致 cpu 飙升

while(true) {
}
  • 解决方式
while(true) {
// 小睡即可
TimeUnit.sleep(1);
}

超时编写

不可能一直等待反馈,可以设置超时时间。

/**
* 循环等待直到获取结果
* @param key key
* @param timeoutInSeconds 超时时间
* @param <T> 泛型
* @return 结果。如果超时则抛出异常
*/
public <T> T loopWaitForValue(final String key, long timeoutInSeconds) {
long startTime = System.nanoTime();
long deadline = startTime + TimeUnit.SECONDS.toNanos(timeoutInSeconds);
//1. 如果没有新回调,或者 key 对应元素不存在。则一直循环
while(ObjectUtil.isNull(map.get(key))) {
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
LOGGER.warn("Loop meet InterruptedException, just ignore it.", e);
}
// 超时判断
long currentTime = System.nanoTime();
if(currentTime >= deadline) {
throw new BussinessException(ErrorCode.READ_TIME_OUT);
}
}
final T target = (T) map.get(key);
LOGGER.debug("loopWaitForValue get value:{} for key:{}", JSON.toJSON(target), key);
//2. 获取到元素之后,需要移除掉对应的值
map.remove(key);
return target;
}

代码地址

loop

countdownlatch

spring-event-listener

java 异步查询转同步多种实现方式:循环等待,CountDownLatch,Spring EventListener,超时处理和空循环性能优化的更多相关文章

  1. Java异步调用转同步的5种方式

    1.异步和同步的概念 同步调用:调用方在调用过程中,持续等待返回结果. 异步调用:调用方在调用过程中,不直接等待返回结果,而是执行其他任务,结果返回形式通常为回调函数. 2 .异步转为同步的概率 需要 ...

  2. java 异步机制与同步机制的区别

    所谓异步输入输出机制,是指在进行输入输出处理时,不必等到输入输出处理完毕才返回.所以异步的同义语是非阻塞(None Blocking). 网上有很多网友用很通俗的比喻  把同步和异步讲解的很透彻 转过 ...

  3. 异步查询转同步加redis业务实现的BUG分享

    在最近的性能测试中,某一个查询接口指标不通过,开发做了N次优化,最终的优化方案如下:异步查询然后转同步,再加上redis缓存.此为背景. 在测试过程中发现一个BUG:同样的请求在第一次查询结果是OK的 ...

  4. 5种必会的Java异步调用转同步的方法你会几种

    转载请注明本文地址:https://www.jianshu.com/p/f00aa6f66281 源码地址:https://gitee.com/sunnymore/asyncToSync Sunny先 ...

  5. java中全面的单例模式多种实现方式总结

    单例模式的思想 想整理一些 java 并发相关的知识,不知道从哪开始,想起了单例模式中要考虑的线程安全,就从单例模式开始吧. 以前写过单例模式,这里再重新汇总补充整理一下,单例模式的多种实现. 单例模 ...

  6. java 多线程并发 synchronized 同步机制及方式

    2. 锁机制 3. 并发 Excutor框架 4. 并发性与多线程介绍 1. synchronized  参考1. synchronized 分两种方式进行线程的同步:同步块.同步方法 1. 方法同步 ...

  7. java异步编程降低延迟

    目录 java异步编程降低延迟 一.ExecutorService和CompletionService 二.CompletableFuture(重要) 三.stream中的parallel(并行流) ...

  8. Java 异步编程 (5 种异步实现方式详解)

    ​ 同步操作如果遇到一个耗时的方法,需要阻塞等待,那么我们有没有办法解决呢?让它异步执行,下面我会详解异步及实现@mikechen 目录 什么是异步? 一.线程异步 二.Future异步 三.Comp ...

  9. 说说Java异步调用的几种方式

    日常开发中,会经常遇到说,前台调服务,然后触发一个比较耗时的异步服务,且不用等异步任务的处理结果就对原服务进行返回.这里就涉及的Java异步调用的一个知识.下面本文尝试将Java异步调用的多种方式进行 ...

随机推荐

  1. 关于java的scanner类

    java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入. 首先使用Scanner类时候需要导包: scanner类的基本语法: Scanner类 ...

  2. .net core+Spring Cloud学习之路 一

    文章开头唠叨两句. 2019年了,而自己参加工作也两年有余了,用一个词来概括这两年多的生活,就是:“碌碌无为”. 也不能说一点收获都没有,但是很少.2019来了,我立志要打破现状,改变自己,突破自我. ...

  3. auto-encoder小记

    1.使用auto-encoder生成手写数字 2.中间code层使用二维向量,使用L2norm处理中间层数据 3.从[-1,1]的矩形框中等间隔选取100个坐标点 作为code值 最终生成图像 后期应 ...

  4. day1||python

    测试题: 0. Python 是什么类型的语言? Python是一种面向对象.解释型.动态类型计算机程序设计语言解释型:程序无需编译成二进制代码,而是在执行时对语句一条一条编译动态类型:在程序执行过程 ...

  5. web方向编程语言最全对比

    web方向编程语言最全对比 目前一般公司的后台用的开发语言大概有以下几种:java,python,php,asp.net,c++,node.js,ruby on rails 等. java 优点:性能 ...

  6. windows下Redis的安装和使用

    1.要安装Redis,首先要获取安装包.Windows的Redis安装包需要到以下GitHub链接找到.链接:https://github.com/MSOpenTech/redis.打开网站后,找到R ...

  7. 左耳听风-ARTS-第2周(2019/3/31-2019/4/6)

    Algorithm 验证括号题(https://leetcode.com/problems/valid-parentheses/).这道题在极客时间上覃超的<算法面试通关40讲>(http ...

  8. eclipse项目改为maven项目导致svn无法比较历史数据的解决办法

    这个问题没有找到合适的答案,最终自己想出了一个解决方案,在此记录下. 问题描述 在将老的eclipse项目重构为maven项目时,我这边是新建了一个maven项目,然后将对应的数据分别放到相应的位置, ...

  9. 【java】抽象类和接口区别

    1.语法层面上的区别 1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法: 2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public sta ...

  10. 新安装mysql,如何提升mysql安全性

    1.修改mysql默认端口,将3306修改为其他端口. 2.设定足够复杂的密码策略并指定访问IP(在user表中可以指定用户可访问的访问IP地址). 3.设定IP访问白名单. 4.设定root用户只能 ...