如何正确的使用Java事件通知(1)(2)
分享于 点击 6614 次 点评:287
并发修改
像上面那样写 StateHolder 很容易遇到并发修改异常(ConcurrentModificationException),即使仅仅限制在一个单线程里面用也不例外。但究竟是谁导致了这个异常,它又为什么会发生呢?
- java.util.ConcurrentModificationException
- at 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)
乍一看这个错误堆栈包含的信息,异常是由我们用到的一个 HashMap 的 Iterator 抛出的,可在我们的代码里没有用到任何的迭代器,不是吗?好吧,其实我们用到了。要知道,写在 broadcast 方法里的 for each 结构,实际上在编译时是会被转变成一个迭代循环的。
因为在事件广播过程中,如果一个监听器试图从 StateHolder 实例里面把自己移除,就有可能导致 ConcurrentModificationException。所以比起在原先的数据结构上进行操作,有一个解决办法就是我们可以在这组监听器的快照(snapshot)上进行迭代循环。
这样一来,“移除监听器”这一操作就不会再干扰事件广播机制了(但要注意的是通知还是会有轻微的语义变化,因为当 broadcast 方法被执行的时候,这样的移除操作并不会被快照体现出来):
- private void broadcast( StateEvent stateEvent ) {
- Set snapshot = new HashSet<>( listeners );
- for( StateListener listener : snapshot ) {
- listener.stateChanged( stateEvent );
- }
- }
但是,如果 StateHolder 被用在一个多线程的环境里呢?

用户点评