java 事件通知_正确获取Java事件通知

java 事件通知

实现观察者模式以提供Java事件通知似乎是一件容易的事。 但是,容易陷入一些陷阱。 这是我在各种场合不慎造成的常见错误的解释……

Java事件通知

让我们从一个简单的bean StateHolder开始,它封装了带有适当访问器的私有int字段state

public class StateHolder {private int state;public int getState() {return state;}public void setState( int state ) {this.state = state;}
}

考虑到我们已经决定我们的bean应该向注册的观察者广播state changes的消息。 没问题! 方便的事件和侦听器定义很容易创建...

// change event to broadcast
public class StateEvent {public final int oldState;public final int newState;StateEvent( int oldState, int newState ) {this.oldState = oldState;this.newState = newState;}
}// observer interface
public interface StateListener {void stateChanged( StateEvent event );
}

…接下来我们需要能够在StateHolder实例上注册StatListeners

public class StateHolder {private final Set<StateListener> listeners = new HashSet<>();[...]public void addStateListener( StateListener listener ) {listeners.add( listener );}public void removeStateListener( StateListener listener ) {listeners.remove( listener );}
}

…最后但并非最不重要的StateHolder#setState必须进行调整以触发有关状态更改的实际通知:

public void setState( int state ) {int oldState = this.state;this.state = state;if( oldState != state ) {broadcast( new StateEvent( oldState, state ) );}
}private void broadcast( StateEvent stateEvent ) {for( StateListener listener : listeners ) {listener.stateChanged( stateEvent );}
}

答对了! 这就是全部。 作为专业人士,我们甚至可能已经实施了此测试驱动程序,并且对我们全面的代码覆盖范围和绿色指示条感到满意。 无论如何,这不是我们从网络教程中学到的吗?

坏消息来了:解决方案有缺陷……

并发修改

给定上述StateHolder ,即使仅在单线程限制内使用,也可以很容易地遇到ConcurrentModificationException 。 但是是谁引起的,为什么会发生呢?

java.util.ConcurrentModificationExceptionat java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)at java.util.HashMap$KeyIterator.next(HashMap.java:1453)at com.codeaffine.events.StateProvider.broadcast(StateProvider.java:60)at com.codeaffine.events.StateProvider.setState(StateProvider.java:55)at com.codeaffine.events.StateProvider.main(StateProvider.java:122)

查看stacktrace会发现该异常是由我们使用的HashMapIterator引发的。 只是我们在代码中没有使用任何迭代器,还是我们? 好吧,我们做到了。 broadcast for each构造的for each基于Iterable ,因此在编译时将其转换为迭代器循环。

因此,侦听器在事件通知期间将自己从StateHolder实例中删除可能会导致ConcurrentModificationException 。 因此,代替处理原始数据结构,一种解决方案是遍历侦听器的快照

这样,侦听器的删除不再会干扰广播机制(但请注意,通知语义也将稍有更改,因为在broadcast执行时快照不会反映这种删除):

private void broadcast( StateEvent stateEvent ) {Set<StateListener> snapshot = new HashSet<>( listeners );for( StateListener listener : snapshot ) {listener.stateChanged( stateEvent );}
}

但是,如果要在多线程上下文中使用StateHolder怎么办?

同步化

为了能够在多线程环境中使用StateHolder ,它必须是线程安全的。 这可以很容易地实现。 将同步添加到类的每个方法中应该可以解决问题,对吗?

public class StateHolder {public synchronized void addStateListener( StateListener listener ) {  [...]public synchronized void removeStateListener( StateListener listener ) {  [...]public synchronized int getState() {  [...]public synchronized void setState( int state ) {  [...]

现在,通过其内部锁来保护对StateHolder实例的读/写访问。 这使公共方法具有原子性,并确保了不同线程的正确状态可见性。 任务完成!

不完全是……尽管该实现线程安全的,但它冒着使用它死锁应用程序的风险。

考虑以下情况: Thread A更改StateHolder S的状态。在通知S的侦听器期间, Thread B尝试访问S并被阻塞。 如果B对即将由S的侦听器之一通知的对象持有同步锁,则我们将陷入死锁。

这就是为什么我们需要缩小同步范围以声明状态并在受保护的段落之外广播事件:

public class StateHolder {private final Set<StateListener> listeners = new HashSet<>();private int state;public void addStateListener( StateListener listener ) {synchronized( listeners ) {listeners.add( listener );}}public void removeStateListener( StateListener listener ) {synchronized( listeners ) {listeners.remove( listener );}}public int getState() {synchronized( listeners ) {return state;}}public void setState( int state ) {int oldState = this.state;synchronized( listeners ) {this.state = state;}if( oldState != state ) {broadcast( new StateEvent( oldState, state ) );}}private void broadcast( StateEvent stateEvent ) {Set<StateListener> snapshot;synchronized( listeners ) {snapshot = new HashSet<>( listeners );}for( StateListener listener : snapshot ) {listener.stateChanged( stateEvent );}}
}

该清单显示了从以前的片段演变而来的实现,该实现使用Set实例作为内部锁提供了适当的(但有些过时的)同步。 侦听器通知发生在受保护的块之外,因此避免了循环等待

注意:由于系统的并发性质,该解决方案不能保证更改通知按发生的顺序到达侦听器。 如果需要有关观察者端的实际状态值的更多准确性,请考虑提供StateHolder作为事件对象的源。

如果事件顺序至关重要的一个会想到一个线程安全的FIFO结构来缓冲在的守卫块根据听众快照一起事件setState 。 只要FIFO结构不为空( Producer-Consumer-Pattern ),一个单独的线程就可以从不受保护的块中触发实际的事件通知。 这应确保按时间顺序排列,而不会冒死机的危险。 我说应该,因为我从未亲自尝试过此解决方案。

鉴于先前实现的语义,使用诸如CopyOnWriteArraySetAtomicInteger类的线程安全类来构成我们的类,可使解决方案的详细程度降低:

public class StateHolder {private final Set<StateListener> listeners = new CopyOnWriteArraySet<>();private final AtomicInteger state = new AtomicInteger();public void addStateListener( StateListener listener ) {listeners.add( listener );}public void removeStateListener( StateListener listener ) {listeners.remove( listener );}public int getState() {return state.get();}public void setState( int state ) {int oldState = this.state.getAndSet( state );if( oldState != state ) {broadcast( new StateEvent( oldState, state ) );}}private void broadcast( StateEvent stateEvent ) {for( StateListener listener : listeners ) {listener.stateChanged( stateEvent );}}
}

由于CopyOnWriteArraySetAtomicInteger是线程安全的,因此我们不再需要受保护的块。 但请稍等! 我们不是只是学习使用快照进行广播,而不是遍历原始集的隐藏迭代器吗?

可能有点令人困惑,但是CopyOnWriteArraySet提供的Iterator已经是快照。 CopyOnWriteXXX集合是专门为此类用例而发明的-如果大小较小则非常有效,针对内容很少变化的频繁迭代进行了优化。 这意味着我们的代码是安全的。

在Java 8中,使用Iterable#forEach结合lambda可以进一步简化broadcast方法。 该代码当然是安全的,因为还在快照上执行了迭代:

private void broadcast( StateEvent stateEvent ) {listeners.forEach( listener -> listener.stateChanged( stateEvent ) );
}

异常处理

这篇文章的最后一部分讨论了如何处理抛出意外RuntimeException的破碎侦听器。 尽管我通常严格选择快速失败的方法,但在这种情况下,让此类异常不予处理可能是不合适的。 特别考虑到该实现可能在多线程环境中使用。

中断的侦听器有两种方式损害系统。 首先,它防止通知我们的柏忌那些观察员被分类。 其次,它可能损害可能没有准备好解决该问题的调用线程。 总结起来,它可能导致多种潜行故障,而最初的原因可能很难追查。

因此,将每个通知屏蔽在try-catch块中可能会很有用:

private void broadcast( StateEvent stateEvent ) {listeners.forEach( listener -> notifySafely( stateEvent, listener ) );
}private void notifySafely( StateEvent stateEvent, StateListener listener ) {try {listener.stateChanged( stateEvent );} catch( RuntimeException unexpected ) {// appropriate exception handling goes here...}
}

结论

如以上各节所示,Java事件通知有几点需要牢记。 确保在事件通知期间遍历侦听器集合的快照,将事件通知置于同步块之外,并在适当的情况下安全地通知侦听器。

希望我能够以一种容易理解的方式解决这些细微问题,并且不会特别弄乱并发部分。 如果您发现一些错误或需要分享其他智慧,请随时使用下面的评论部分。

翻译自: https://www.javacodegeeks.com/2015/03/getting-java-event-notification-right.html

java 事件通知

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/338500.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言 | 二维数组

C语言二维数组的定义一般形式 类型说明符 数组名[常量表达式][常量表达式]int a[10][6],b[3][4];用矩阵形式表示二维数组&#xff0c;是逻辑上的概念&#xff0c;能形象地表示出行列关系&#xff0c;而在内存中&#xff0c;各元素是连续存放的&#xff0c;不是二维的&#xff0…

C语言 | 一维数组

C语言一维数组的定义一般形式类型符 数组名[常量表达式]C语言使用一维数组注意事项数组名的命名规则和变量名相同&#xff0c;遵循标识符命名规则。在定义数组时&#xff0c;需要指定数组中元素的个数&#xff0c;方括号中的常量表达式用来表示元素的个数&#xff0c;即数组长度…

计算机网络教学方式探讨论文,学生老师论文,关于关于高中计算机网络教学效率提升相关参考文献资料-免费论文范文...

导读:本论文是一篇免费优秀的关于学生老师论文范文资料&#xff0c;可用于相关论文写作参考。(山东省新泰市第一中学 山东新泰 271200)摘 要&#xff1a;随着计算机网络的发展和快速普及,计算机网络教学已经逐步进入高中课堂中,与传统教学模式优势互补,成为学生学习的好帮手.该…

小程序可不可以汉字命名_不可将您的方法命名为“等于”

小程序可不可以汉字命名&#xff08;当然&#xff0c;除非您确实重写了Object.equals() &#xff09;。 我偶然发现了用户Frank提出的一个非常奇怪的Stack Overflow问题 &#xff1a; 为什么Java的Area&#xff03;equals方法不能覆盖Object&#xff03;equals&#xff1f; 有…

怎样删去csv中重复行_4个锦囊,祝你快速删去Excel中的重复数据

快速删去重复数据&#xff0c;雷哥在这里提供4种超简单的解决方法&#xff0c;大家一看就会。为了更加直接明了地说明&#xff0c;雷哥在这里通过具体案例进行讲解。案例&#xff1a;下图B列日期中&#xff0c;橘黄色部分是我们已经标记好颜色的重复项。要求&#xff1a;快速删…

C语言通过printf()设置输出显示方式

在调试程序时多数情况下要输出很多提示信息&#xff0c;通过控制输出字体的颜色的显示方式可以方便我们快速查看有用的信息&#xff0c;而printf()的终端转义就为我们提供了这样的手段。我们大家知道在程序结尾加上\n就可在输出的时候换行&#xff0c;其实这就是个转义字符。而…

如何取消计算机阻止安装程序,Win10电脑安装软件提示“你必须取消阻止该发布者才能运行此软件”怎么办...

&#xfeff;我们在使用win10系统的时候&#xff0c;都会在电脑中安装各种各样的软件&#xff0c;但是有时候在安装软件的还是&#xff0c;会遇到一些提示&#xff0c;比如“你必须取消阻止该发布者才能运行此软件”&#xff0c;导致安装软件失败&#xff0c;这该怎么办呢&…

perl大骆驼和小骆驼_快速的骆驼和云消息传递

perl大骆驼和小骆驼Apache Camel是一个流行的&#xff0c;成熟的开源集成库。 它实现了企业集成模式 &#xff0c;这是在集成分布式系统时经常出现的一组模式。 过去&#xff0c;我写过很多关于Camel的文章&#xff0c; 包括为什么我比Spring Integration更喜欢它 &#xff0c;…

mvc 事务层切换数据源_Mvc 与 Flux 与 Redux的一些思考

MVC模型 解决问题以及不足解决问题为了解决业务逻辑和界面渲染逻混在一起 MVC流程图2. 不足由于 Model 对外直接暴露了 set 和 on 方法&#xff0c;导致 View 层可以随意改变 Model 中的值&#xff0c;也可以随意监听 Model 中值的变化。这样的设定最终会导致一个庞大的 Model …

计算机语言缺省,揭秘物联网必学语言——C语言与C++的区别

C语言虽说经常和C在一起被大家提起&#xff0c;但可千万不要以为它们是一个东西。有很多人会有这样的疑问&#xff1a;C语言和C有什么区别呢&#xff1f;C是在C语言的基础上发展来的&#xff0c;但是并不是C比C语言高级&#xff0c;两者的编程思想不一样&#xff0c;应用的领域…

C/C 语言printf()

1.调用格式为 printf("<格式化字符串>", <参量表>); 其中格式化字符串包括两部分内容: 一部分是正常字符, 这些字符将按原样输出; 另一部分是格式化规定字符, 以"%"开始, 后跟一个或几个规定字符, 用来确定输出内容格式。参量表是需要输出的一…

tf/idf_Neo4j:带密码的TF / IDF(和变体)

tf/idf几周前&#xff0c;我写了一篇博客文章&#xff0c;介绍了如何使用scikit-learn在HIMYM成绩单上运行TF / IDF&#xff0c;以按情节找到最重要的短语&#xff0c;然后我很好奇在Neo4j中很难做到。 我首先将Wikipedia的TF / IDF示例之一翻译为cypher&#xff0c;以查看该算…

公路多孔箱涵设计_【公路常识中篇】公路路基边坡滑坡防护设计和预防

【公路常识中篇】公路路基边坡滑坡防护设计和预防来源&#xff1a;网络 公路路基基本知识&#xff0e;内容包括&#xff1a;公路路床&#xff1b;公路路堤&#xff1b;公路路基压实&#xff1b;影响公路路基稳定因素&#xff1b;公路路堑&#xff1b;公路路基沉陷&#xff1b;公…

按照计算机系统结构分类存储器可分为,存储器分类,存储器的分级结构

描述1、存储器概述存储器是计算机系统中的记忆设备&#xff0c;用来存放程序和数据。构成存储器的存储介质&#xff0c;目前主要采用半导体器件和磁性材料。存储器中最小的存储单位就是一个双稳态半导体电路或一个CMOS晶体管或磁性材料的存储元&#xff0c;它可存储一个二进制代…

spark 流式计算_流式传输大数据:Storm,Spark和Samza

spark 流式计算有许多分布式计算系统可以实时或近实时处理大数据。 本文将从对三个Apache框架的简短描述开始&#xff0c;并试图对它们之间的某些相似之处和不同之处提供一个快速的高级概述。 阿帕奇风暴 在风暴 &#xff0c;你设计要求的T opology实时计算的图&#xff0c;然…

嵌入式开发C语言中的uint8_t

在嵌入式开发中的C语言代码中&#xff0c;经常可以看到类似uint8_t、uint16_t、uint32_t、uint64_t这种数据类型&#xff0c;在教材中却从来没见过。实际上这些数据类型都是某种数据类型的别名。比如&#xff0c;在定义函数时用到了uint8_t。右键“uint8_t”&#xff0c;单击“…

多模态语义分析_情感分析、多模态NLP、多语言翻译...这场NLP知识盛宴不可错过!...

AI科技评论按&#xff1a;2020年12月20日&#xff0c;由中国计算机学会自然语言处理专业委员会(CCF-NLP)发起&#xff0c;联合AI研习社及各个知名高校开展的“CCF-NLP走进高校”系列高校NLP研究分享报告会第六期——华中师范大学站&#xff0c;通过线上会议直播的方式进行。本期…

电瓶车续航测试软件,重点看续航 测试2020款蔚来ES8 485KM

时间回到2019年12月28日&#xff0c;一场隆重的“NIO DAY 2019”在深圳举行&#xff0c;2020款ES8便是在那场大秀中首次亮相。5个月之后&#xff0c;2020款蔚来ES8已于4月19日正式开始交付&#xff0c;我们也在交付之日对新车进行了试驾体验。所以今天就不多废话了&#xff0c;…

海盗云商插件_推销自己的海盗猫王运营商

海盗云商插件因此&#xff0c;Java没有Elvis运算符&#xff08;或者&#xff0c;更正式地讲&#xff0c;它没有null合并运算符或null安全成员选择&#xff09;……虽然我个人不太在意它&#xff0c;但有些人似乎很喜欢它。 当一位同事需要几天后&#xff0c;我坐下来探讨了我们…

英文期刊催稿信模板_手把手教你写投稿信,另附查尔斯沃思高质量模板

导语本文是查尔斯沃思作者服务关于学术论文写作系列文章的最后一篇&#xff0c;我们邀请英国编辑团队资深成员&#xff0c;根据其自身丰富的撰稿经验&#xff0c;为中国作者呈现系统全面的写作指导建议&#xff0c;我们将其翻译成中文&#xff0c;方便大家理解。希望本系列文章…