总的结论就是:不推荐使用JDK自带的观察者API,而是自定义实现,可是能够借鉴其好的思想。java
该接口十分简单,是各个观察者须要实现的接口算法
package java.util; public interface Observer { void update(Observable o, Object arg); }
也十分直接,就是使用顶级父类 Object 作参数类型,而后本身能够定义一个参数封装的类。数组
另外,第一个参数 Observable 就是所谓的主题接口,JDK 给实现了,可是实现的不是特别好。估计 Oracle 也懒得改了,这里加上这个参数,很是好,由于能让观察者知道究竟是哪一个主题通知的“我”。安全
这是该API最大的问题,它使用的是类,而不是接口去扩展。微信
以下源码,删除了大量英文注释,改成更精简的形式,并加入了详尽的批注并发
import java.util.Observer; import java.util.Vector; /** * 这里其实就是主题接口的角色,只不过 JDK 的实现很烂——居然用类封装的,这是公认的槽点之一。 */ public class Observable { private boolean changed = false; private Vector obs; // 看到这里,其实也知道,这个API不只太古老,并且还没人维护了,用的仍是最老的,被淘汰的 Vector 实现的动态数组 public Observable() { obs = new Vector(); } // 注册观察者,线程安全,这是优势之一,能够借鉴 public synchronized void addObserver(Observer o) { if (o == null) // 提升代码健壮性 throw new NullPointerException(); if (!obs.contains(o)) { // 注册时会去重,自定义实现须要注意也去重 obs.addElement(o); } } // 观察者取消注册 public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } // 基于拉模型的通知方法 public void notifyObservers() { notifyObservers(null); } // 基于推模型 public void notifyObservers(Object arg) { // 一个临时数组,用于并发访问被观察者时,保存观察者列表的当前状态——这就是基于备忘录模式的简单应用。 Object[] arrLocal; // 在获取到观察者列表以前,不容许其余线程改变观察者列表 synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); // 重置变化标记位为 false clearChanged(); } // 主题类释放锁,可是并不影响线程安全,由于加锁以前已经将观察者列表复制到临时数组 arrLocal // 在通知时咱们只通知数组中的观察者,当前删除和添加观察者,都不会影响咱们通知的对象 for (int i = arrLocal.length - 1; i >= 0; i--) ((Observer) arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }
一、使得主题具有了很大的伸缩性源码分析
假如没有 setChanged,那么一旦主题的状态变了,就不得不当即通知订阅者,这不是很合理,须要一个缓冲——setChanged,如JDK同样,在notify方法中作判断,若是状态的变化达到了一个阈值,在设置 setChanged 条件,这时候才会真的通知,这个条件以及阈值的设置能够在主题类(继承了Observalbe类)的业务代码中实现。性能
JDK还提供了配套的检查该标志的方法。this
二、能筛选订阅者spa
只有有效通知能够调用 setChanged。好比,微信朋友圈的一条状态,好友 A 点赞,后续该状态的点赞和评论并非每条都通知 A,只有 A 的好友触发的操做才会通知A——好友才会调用setChanged,这个业务逻辑就能够借鉴JDK
三、能实现通知的撤销
主题中能够设置不少次的 setChanged,好比在一个事务中,在最后因为某种缘由,事务失败,那么通知也必须取消,此时可使用 clearChanged 方法轻松解决问题
四、主题的主动权控制
setChanged 和 clearChanged 方法均为 protected,而 notifyObservers 方法为 public,这就致使存在外部随意调用 notifyObservers 的可能,可是外部没法调用 setChanged,所以真正的控制权属于主题——即便外部能调用主题的通知方法,也是然并卵的
主题类即便在清理了状态位以后就释放锁,可是并不影响通知方法的线程安全性,由于加锁以前已经将观察者的列表复制到了一个临时数组 arrLocal——数组是不可变的,局部的。在释放锁后,通知观察者们,可是只通知该临时数组中保存的观察者的们快照,在通知的时候,即便有删除和新的观察者注册,也不会影响通知的过程。
public void notifyObservers(Object arg) { // 一个临时数组,用于并发访问被观察者时,保存观察者列表的当前状态——这就是基于备忘录模式的简单应用。 Object[] arrLocal; // 在获取到观察者列表以前,不容许其余线程改变观察者列表 synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); // 重置变化标记位为 false clearChanged(); } for (int i = arrLocal.length - 1; i >= 0; i--) ((Observer) arrLocal[i]).update(this, arg); }
上面的实现中,能够发现一个问题,update 是观察者接口中的方法,是各个具体的观察者须要实现的方法,若是具体观察者的 update 方法有机会抛出异常,那么若是 RD 没有捕获,就会把异常抛出,致使整个通知过程失败,这里也是为何,不推荐使用该接口。
在本身实现的时候,能够把观察者的 update 方法,用异常控制块包起来,保证通知过程能完整执行。
主题也持有了观察者的引用,若是未正常处理——及时的从主题中删除废弃的观察者,会致使大量的废弃观察者没法被回收。这里其实主要仍是业务代码的问题。
若是观察者具体实现代码有问题,可能会致使主题和观察者对象造成循环引用,在某些采用引用计数的垃圾回收器可能致使没法回收。可是,现代GC中,这种问题不会出现,引用计数器算法早已经被放弃使用。
先说结论——Vector是最旧的 List 实现,再也不推荐使用。
这又是一个槽点,当初实现 JDK 的观察者 API 的时候,可能动态数组用 vector 实现比较好,可是如今早就是推荐使用 Arraylist 了。虽然,vector 与 ArrayList 类似,可是:
一、Vector 是线程安全的list集合
Vector 彻底基于 synchronized 实现同步,虽然它的操做与 ArrayList 几乎同样,可是不少时候咱们不须要那么重的实现,毕竟加锁会影响性能。故通常直接使用ArrayList,并且,必定要实现线程安全的动态数组,也轮不到用 Vector,可使用 JUC 中的 CopyOnWriteArraylist 等容器。或者用 Collections 类的同步 List 静态方法来转换为同步List
二、Vector 的部分方法名太长,ArrayList 的对应实现方法名短些,便于阅读,目前仍在使用 Vector 的软件,基本都是为了兼容旧库和懒得改
三、Vector 的容量增加性能不好
Vector 是可变数组,初始 length 是 10 ,若是超过 length 时,会以 100% 比率增加 length,即变成20,因此存在内存浪费的现象,而 Arraylist 的 length 是以 50% 比率增长,因此相比来讲,内存使用率较高
看源码得知,主题通知观察者的顺序,是 tmd 的倒叙,致使通知观察者的顺序和注册的顺序不同,若是业务代码对顺序有要求,就很差弄了
众所周知,Java 没有多继承机制,若是具体主题除了继承主题类外, 还想继承其余业务类,就无法儿写了。典型的违背了“组合(聚合)优于继承的”设计原则。故通常自定义的实现比较多,也不难。虽然 JDK 给咱们作了封装,可是不少不少时候,业务需求复杂,JDK 的 API 并不能知足咱们的需求。