JAVA—将异步API改成同步调用的方法

看起来有点绕,说一个具体的场景应该容易理解一点:

  1. 从APP向服务器发送一个改变灯光的HTTP请求,服务器返回执行是否成功的结果;
  2. 服务器接收到HTTP请求后,通过MQTT向台灯下发控制指令,等待台灯回复①,返回结果;
  3. 台灯接收到MQTT指令后,执行命令,然后通过MQTT回复消息给服务器②;
  4. 通常来说,后台会有一个专门的服务订阅一个固定的Topic,接收台灯的消息,所以①中,不可能是处理HTTP请求的线程订阅Topic,那么订阅Topic的服务,是怎么把消息通知到HTTP线程的呢?

最开始我的做法是,订阅Topic的服务,将消息解析后放入redis,然后HTTP线程在下发消息后,通过redis的超时读机制,阻塞线程,获取处理结果,如果超时未返回则认为不成功。
该方法的弊端在于……就为了这个要求,强制绑定redis,带价太大,而且实现也不是很好。

在Golang中有协程和channel的概念,协程之间可以通过channel去通信,只要设计合理,就可以很容易的实现该功能,但是在JAVA中,没有channel,替代品就是Object.wait()和Object.notify()线程等待/通知机制,直接上代码!

package com.shrimp.dome;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    private static ConcurrentHashMap<String, Object> msgManager = new ConcurrentHashMap<>();

    public static void main(String[] args) throws InterruptedException {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 1; i < 10; i++) {
            cachedThreadPool.submit(new WaitThread(i));
        }
        Thread.sleep(5000);
        new Thread(new Connsumer()).start();
    }

    static class Connsumer implements Runnable {
        @Override
        public void run() {
            msgManager.forEach((key, value) -> {
                System.out.println("dell with : " + ((Event) value).messageId);
                synchronized (value) {
                    value.notify();
                }
            });
        }
    }

    static class WaitThread implements Runnable {
        private int count;

        WaitThread(int count) {
            this.count = count;
        }

        @Override
        public void run() {
            Event event = new Event();
            event.messageId = count + "";
            synchronized (event) {
                msgManager.put(event.messageId, event);
                try {
                    long timeout = 1000 * 10;
                    long begin = System.currentTimeMillis();
                    event.wait(timeout);
                    long end = System.currentTimeMillis();
                    if (end - begin < timeout) {
                        System.out.println("continue by notify");
                    } else {
                        System.out.println("continue after timeout");
                    }
                    msgManager.remove(event.messageId);
                    System.out.println("WaitThread cont continue : " + count);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    static class Event {
        String messageId;
    }
}

实现思路很简单,首先需要有一个全局的Map,以便两个线程都能访问到,一般通过消息ID作为键,利于检索;
HTTP线程在发送设备控制请求后,创建事件对象event,通过synchronized获取对象锁,然后放入上面的全局Map中,调用event.wait(timeout),这里最好是wait(timeout)而不是wait(),否则改线程会一直阻塞占资源。等待其他线程notify后,在map删除对应的event释放内存,就可以回复response了;
对于订阅Topic的服务,就可以通过消息带的消息ID,取得对应的event,然后调用event.notify()方法,就可以通知到HTTP线程继续执行。
通过上面的方法,就完成了同步回复异步消息的功能了。

不知道这样的方法对不对,但是测试下来是没问题的,可能在效率和实现方面有点抓急。如果有更好的方法,欢迎留言告诉我,谢谢!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值