背景

我的项目中,有一个需求是监听文件系统的变化,然后进行其他的操作,一旦在监听器中直接处理业务逻辑,就很容易引发很多bug(其实主要是早期设计不好,同步代码和锁还有莫名其妙的东西满天飞,业务逻辑都堆在一起了),比较难以修改和使用,最终我决定采用反应器模式来重写这部分逻辑。

项目主要用到了JNotify和lombok这两个东西,前者是监听文件变化的,后者就不多说了,我想应该都知道。

概述

什么是反应器模式呢?反应器基本上有这样几个部分:

  1. 分发器:分发一个事件到处理器中进行处理,一般来说是一个线程,他可以对事件的类型之类的进行判断,然后分配给合适的处理器,在典型的reactor模式中,这都是在同一个线程内进行的,分发器的主体应该含有一个事件循环,读取容器中存在的事件然后分发和处理他们。
  2. 监听器或连接器或者类似的东西:收集事件,然后将他们放入分发器的容器中,稍后这些事件就会被分发器发现并且分发到处理器中。
  3. 处理器:具体进行某种具体的业务处理。

那么对于我的项目来说,首先应该收集文件事件到队列中,然后在分发器出队事件放入处理器,整个过程相比直接在listener中处理具体逻辑的做法,这是更加有序而可控的,对于这样一个业务来说,稳定性十分重要。

大致结构

我使用了一个单独的Thread作为分发器的线程,内部的run方法放置事件循环,然后同时继承listener,监听事件,看起来就像是这样:

@Component
public class ReactorDispatcher implements JNotifyListener,Runnable {private CycleBarrier monitor = new CycleBarrier(2);private Deque<FSEvent> queue = new ArrayDeque<>(); private int watcherId;private Thread thread;private boolean state = true;// 这里使用了lombok// data注解会自动为下面的字段添加Getter和Setter// allArgsConstructor会给这个类添加一个默认的含有全部字段的构造方法@Data@AllArgsConstructorprivate class FSEvent {private String path;private String name;private int type;}// 初始化方法,spring完成装配后会首先执行他。@PostConstructpublic void initialize() {try {// 使用JNotify开始监听文件系统watcherId = JNotify.addWatcher(new File("files").getAbsolutePath(),JNotify.FILE_CREATED|JNotify.FILE_DELETED);// 创建线程,其实是不是也可以作为守护线程处理呢,// 不过守护线程的特点我不熟悉,就还是用来普通的方法。thread = new Thread(this);thread.setName("reactor - fs listener");thread.start();}catch(Exception e) {}}// 销毁方法,清理用。@PreDestorypublic void destory() {try {JNotify.removeWatcher(watcherId);state = false;} catch(Exception e) {}}/*** 这几种方法是监听器的方法,他会在文件发生变化的时候被回调。* 通过这些方法可以将监听到的内容变为事件对象,然后存入队列。*/public void fileCreated(int type,String path,String name) {FSEvent event = new FSEvent(JNotify.FILE_CREATED,path,name);this.emit(event);}/*** 这几种方法是监听器的方法,他会在文件发生变化的时候被回调。* 通过这些方法可以将监听到的内容变为事件对象,然后存入队列。*/public void fileRemoved(int type,String path,String name) {FSEvent event = new FSEvent(JNofiy.FILE_DELETED,path,name);this.emit(event);}// 省略不必要的监听方法,其实JNotify还有两个监听方法,这里就不在列举了。// 业务处理private void resolveEvent(FSEvent event) {// 在这里进行业务处理,ReactorDispatcher还会进行批量注入,将实现某个接口的实例作为list// 注入进来(但是这段代码里面我没写,因为这个是需要根据实际情况设计的)即通过接// 口注入一组业务逻辑的处理器// 然后在这里根据type进行分发,虽然标准的reactor是单线程的,但是我的业务处理很多// 时候是需要消耗相当的时间,所以我在这里用了线程池,所有的event都是异步处置,根据不同的// 场景,时间消耗较低的可以直接单线程。当然以nio为基础的话,这里完全可以单线程的。// 不过nio处理相对复杂一点,容易出现回调地狱这种问题,因此在没有好的处理方式之前,// 还不如直接线程池异步处理,只要小心锁的问题就是了。}public void emit(FSEvent event) {if(queue.isEmpty()) {quque.addFirst(event);try {monitor.await();} catch(Exception e) {}} else {quque.addFirst(event);}}public void run() {while(state) {try{// 阻塞线程,直到Event出现。monitor.await();if(queue.isEmpty()) {continue;}// removeLast 在没有下一个的时候会抛出空指针异常,需要catch一下,不然线程有// 异常后会直接退出,连报错都没有。之前遇到线程莫名自动退出的问题,就是因为run// 里面出现了异常。while(!quque.isEmpty()) {FSEvent event = queue.removeLast();this.resolveEvent(event);}mointor.reset();}catch(Exception e) {}}} }

可以看到,相比直接在listener进行处理,收集事件然后统一进行分配,这样更加容易控制,而且详细的逻辑可以不用和这个监听和分发的结构混在一起,整体结构更加清晰明确。

当然和web的reactor模式相比,我编写的有比较明显的差别,这种差别还是来自具体的应用场景,主要的思路还是差不多的。

大概的思路就像上图这样。


如果分发器线程没有事件,一直在循环,什么都不做,是会消耗cpu资源的,所以增加了一个CycleBarrier,这个东西的作用,是在await的次数达到指定次数后,会释放锁,让线程继续执行,这里设置为2,当分发器线程没有event的时候,会进行一个await,这个时候分发器被锁住,线程阻塞,一直到出现event,在event出现后,CycleBarrier的await达到2,锁被释放,分发器可以继续执行。

其实反应器模式应该就是在无事件的时候阻塞的,之前我没有想好该怎么处理,后来读某个源码的时候看到了CountDownLatch和CycleBarrier,就突然想到可以用在这里。

2020 - 11 - 18 修改。

websocket.onmessage回调没反应_Java笔记:反应器模式的简单运用相关推荐

  1. websocket.onmessage回调没反应_等待A股暴跌回调补四缺口倒计时第二十四天——8月19日总结...

    今日行情走势分析 昨天晚上写了篇文章<从历史经验分析上证指数拉高后离暴跌就剩不到10个交易日 >,很多韭菜不以为然的就来了句,玩技术的都已经破产等等带有讽刺的语言,最近我本人已经不爱怼回去 ...

  2. 用Websocket聊天完整demo,做笔记用,各位大佬多多指教

    //企业端 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806160850444.png?x-oss-process=image/watermark,t ...

  3. android网络请求回调管理,Android网络请求回调没正常走 处理方案

    处理类 import android.os.Handler; import android.os.Looper; import android.os.Message; /** * * 考虑可能回调没正 ...

  4. 【AGC】iOS的applink的回调没反应问题分析

    问题背景 在 iOS平台集成AGCAppLinking是遇到了问题和疑惑.提问如下: 使用universallink 或者 url schema的app link都能拉起app,但是判断是否app l ...

  5. 【学习笔记】网络流算法简单入门

    [学习笔记]网络流算法简单入门 [大前言] 网络流是一种神奇的问题,在不同的题中你会发现各种各样的神仙操作. 而且从理论上讲,网络流可以处理所有二分图问题. 二分图和网络流的难度都在于问题建模,一般不 ...

  6. Android:安卓学习笔记之MVP模式的简单理解和使用

    Android MVP模式的简单理解和使用 MVP模式 1. 为什么使用MVP模式? 1.1.实例说明 2.一步步让你理解MVP 2.1.MVP实现第一步, 将页面拆分为M/V/P三个模块 2.2. ...

  7. HTML+JS+websocket 实现联机“游戏王”对战(九)- 实现简单websocket通信

    目录: 游戏王联机卡牌对战 1 - 前言 游戏王联机卡牌对战 2 - 联机模式 游戏王联机卡牌对战 3 - 界面布局 游戏王联机卡牌对战 4 - 卡组系统 游戏王联机卡牌对战 5 - 卡片选中系统 游 ...

  8. tensorflow学习笔记二——建立一个简单的神经网络拟合二次函数

    tensorflow学习笔记二--建立一个简单的神经网络 2016-09-23 16:04 2973人阅读 评论(2) 收藏 举报  分类: tensorflow(4)  目录(?)[+] 本笔记目的 ...

  9. 用设计模式去掉没必要的状态变量 —— 状态模式

    这是设计模式系列的第四篇,系列文章目录如下: 一句话总结殊途同归的设计模式:工厂模式=?策略模式=?模版方法模式 使用组合的设计模式 -- 美颜相机中的装饰者模式 使用组合的设计模式 -- 追女孩要用 ...

  10. 红橙Darren视频笔记 代理模式 动态代理和静态代理

    红橙Darren视频笔记 代理模式 动态代理和静态代理(Android API 25) 关于代理模式我之前有过相关的介绍: https://blog.csdn.net/u011109881/artic ...

最新文章

  1. 《软件工程》总结——第十一章
  2. 数据库副本的自动种子设定(自增长)
  3. unityios开发--加载视频以及加载完成之后自动跳转 .
  4. 深入浅出InfoPath——动态获取InfoPath中的命名空间
  5. java拉丁正方形_LeetCode JAVA解题---824. 山羊拉丁文
  6. dos虚拟机如何全屏显示_实用工具 | 虚拟机软件VirtualBox详细使用介绍
  7. 一套完整的Selenium自动化测试框架设计实战,这次38K, 妥了
  8. win8中文_免费文字转语音软件,支持男女中文英文混读,且用且珍惜!!
  9. 都是执行软件测试,差异点在那里
  10. go语言源自python语言_别再用Python编写机器学习基础设施啦,Go语言它不香吗?...
  11. 如何在 Mac 上查找路由器 IP 地址?
  12. 去除移动端alert/confirm的网址(url)
  13. 【2022跨年】最浪漫的表白烟花,送给新的一年的自己(源码)
  14. Spring注解原理的详细剖析与实现
  15. IT项目管理流程以及每个步骤用到的文档
  16. ScreenToClient And ClientToScreen
  17. 观点:游戏模块分析总结
  18. Windows注册表开机自启,右键菜单,运行的位置介绍
  19. 【转】常用的版本控制软件
  20. 2020电子信息夏令营(湖大,吉大,中山,武大,浙大)

热门文章

  1. 对象的序列化与反序列化Demo
  2. Java 编程需要注意的细节
  3. 腾讯打免费牌争抢市场 马化腾表示QQ旋风免费
  4. 9.Linux性能诊断 --- Web应用安全:攻击,防护与检测,IPv6,容器安全
  5. 14.UNIX 环境高级编程--高级IO
  6. 3.2 Zend_Db_Select
  7. 1. WordPress 安装
  8. mysql中的join问题:mysql不支持full join
  9. reset.css(样式重置)
  10. DOM.getBoundingClientRect()