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

本文介绍了在Java中如何将异步操作转换为同步操作的两种方法:循环等待和使用`CountDownLatch`。通过示例代码详细展示了如何在等待异步结果返回时避免CPU资源浪费,并提供了超时处理的思路。
摘要由CSDN通过智能技术生成

异步转同步

业务需求

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

正常处理逻辑

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

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

如何转换为同步

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

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

思路

发起异步操作

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

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

循环等待

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 泛型

* @return 结果。如果超时则抛出异常

*/

public 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;

}

代码地址

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

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

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

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

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

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

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

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

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

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

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

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

java异步编程降低延迟

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

java笔记--关于线程同步(7种同步方式)

关于线程同步(7种方式) --如果朋友您想转载本文章请注明转载地址"http://www.cnblogs.com/XHJT/p/3897440.html"谢谢-- 为何要使用同步? ...

java 获取classpath下文件多种方式

java 获取classpath下文件多种方式 一:properties下配置 在resources下定义server.properties register.jks.path=classpath\: ...

随机推荐

CSS3动画事件

CSS3 的动画效果强大,在移动端使用广泛,动画执行开始和结束都可以使用JS来监听其事件. animationstart animationend 以下是一个示例 ...

oracle ebs应用产品安全性-交叉验证规则

转自: http://blog.itpub.net/298600/viewspace-625138/ 定义: Oracle键弹性域可以根据自定义键弹性域时所定义的规则,执行段值组合的自动交叉验证.使用 ...

Auto Updating the exe from a network location when application starts z

http://www.codeproject.com/Tips/869588/Auto-Updating-the-exe-from-a-network-location-when?msg=499218 ...

Android+Jquery Mobile学习系列(4)-页面跳转及参数传递

关于页面转场,这个必须得专门列出来说明一下,因为Jquery Mobile与普通的Web发开有一些区别,这个对于新手如果不了解的话,就会钻到死胡同.撸主前段时间就是很急躁地上手开发程序,结果在页面转场 ...

sql查询统计,根据新闻类别ID统计,没有数据显示0

有两张表,新闻信息表MessageInfo和新闻类别表MessageType.表结构如下: 然后需要实现下面这种查询结果: 这个是我面试时遇到的,上面的新闻类型是乱写的.当时没有做出来,然后回来又研究 ...

[Linux]信号集和sigprocmask信号屏蔽函数

一.概述 系统提供这样一种能力,就是创建一个信号集,然后传递给信号屏蔽函数,从而屏蔽向该进程发送的信号. 有一点需要注意的是,不能屏蔽SIGKILL和SIGSTOP信号. 信号集是sigset_t类型 ...

gitlab 502

经过一个下午的查找终于发现了错误,原来是在服务器上还开启了一个tomcat服务,占用了8080端口,使GitLab的unicorn服务不能开启. 最后在/etc/gitlab/gitlab.rb 中做 ...

P4381 [IOI2008]Island(基环树+单调队列优化dp)

P4381 [IOI2008]Island 题意:求图中所有基环树的直径和 我们对每棵基环树分别计算答案. 首先我们先bfs找环(dfs易爆栈) 蓝后我们处理直径 直径不在环上,就在环上某点的子树上 ...

Laravel JsonResponse数组获取

有一个JsonResponse数据的格式如下: object(Illuminate\Http\JsonResponse)[474] protected 'data' => string '{&q ...

js如何切割字符串

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值