异步转同步

业务需求

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

  • 正常处理逻辑

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

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

  • 如何转换为同步

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

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

思路

  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. app内嵌vue h5,安卓和ios拦截H5点击事件

    安卓和ios拦截h5点击事件,这个函数事件必须是暴漏在window下的 安卓和ios拦截普通h5函数: <div onclick = "show(),window.android.sh ...

  2. FixedUpdate()使用

    当MonoBehaviour启用时,其 FixedUpdate在每一帧被调用. 处理Rigidbody时,需要用FixedUpdate代替Update.例如:给刚体加一个作用力时,你必须应用作用力在F ...

  3. 初探OpenCL之Mac OS上的hello world示例

    了解了深度学习的崛起,引起了目前OpenCL的需求,大致了解一下. 相关内容:http://blog.csdn.net/leonwei/article/details/8880012 本身OpenCL ...

  4. Development descriptor

    部署描述符指的是配置文件对于一个假象部署到一些容器/发动机. 在Java平台,企业版部署描述符描述组件.模块或应用程序(例如web应用程序或者企业应用程序)应该被部署.它指导部署工具部署具有特定容器选 ...

  5. Merging one UE4 project into another.

    Merging can be done by right clicking on folder in the content directory in the editor and selecting ...

  6. Automated EBS Snapshots using AWS Lambda & CloudWatch

    Overview In this post, we'll cover how to automate EBS snapshots for your AWS infrastructure using L ...

  7. Yii2 设计模式——工厂方法模式

    工厂方法模式 模式定义 工厂方法模式(Factory Method Pattern)定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个.工厂方法让类吧实例化推迟到子类. 什么意思?说起来有这么 ...

  8. C#使用CH341 SPI模块读写SD卡

    SD卡相关CMD命令 ;//卡复位 ; ;//命令9 ,读CSD数据 ;//命令10,读CID数据 ;//命令12,停止数据传输 ;//命令16,设置SectorSize 应返回0x00 ;//命令1 ...

  9. 筛选最小值---verilog

    筛选最小值---verilog `timescale 1ns / 1ps /////////////////////////////////////////////////////////////// ...

  10. 基于MATLAB搭建的DDS模型

    基于MATLAB搭建的DDS模型 说明: 累加器输出ufix_16_6数据,通过cast切除小数部分,在累加的过程中,带小数进行运算最后对结果进行处理,这样提高了计算精度. 关于ROM的使用: 直接设 ...