JDK 自带的观察者模式源码分析以及和自定义实现的取舍

setChanged 这个开关的作用——可以借鉴思想

1、使得主题具备了很大的伸缩性

假如没有 setChanged,那么一旦主题的状态变了,就不得不立即通知订阅者,这不是很合理,需要一个缓冲——setChanged,如JDK一样,在notify方法中做判断,如果状态的变化达到了一个阈值,在设置 setChanged 条件,这时候才会真的通知,这个条件以及阈值的设置可以在主题类(继承了Observalbe类)的业务代码中实现。

JDK还提供了配套的检查该标志的方法。

2、能筛选订阅者

只有有效通知可以调用 setChanged。比如,微信朋友圈的一条状态,好友 A 点赞,后续该状态的点赞和评论并不是每条都通知 A,只有 A 的好友触发的操作才会通知A——好友才会调用setChanged,这个业务逻辑就可以借鉴JDK

3、能实现通知的撤销

主题中可以设置很多次的 setChanged,比如在一个事务中,在最后由于某种原因,事务失败,那么通知也必须取消,此时可以使用 clearChanged 方法轻松解决问题

4、主题的主动权控制

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 方法,用异常控制块包起来,保证通知过程能完整执行。

OOM 的隐患(微不足道)

主题也持有了观察者的引用,如果未正常处理——及时的从主题中删除废弃的观察者,会导致大量的废弃观察者无法被回收。这里其实主要还是业务代码的问题。

如果观察者具体实现代码有问题,可能会导致主题和观察者对象形成循环引用,在某些采用引用计数的垃圾回收器可能导致无法回收。但是,现代GC中,这种问题不会出现,引用计数器算法早已经被放弃使用。

持有观察者的集合类 Vector 的性能问题

先说结论——Vector是最旧的 List 实现,不再推荐使用。

这又是一个槽点,当初实现 JDK 的观察者 API 的时候,可能动态数组用 vector 实现比较好,但是现在早就是推荐使用 Arraylist 了。虽然,vector 与 ArrayList 相似,但是:

1、Vector 是线程安全的list集合

Vector 完全基于 synchronized 实现同步,虽然它的操作与 ArrayList 几乎一样,但是很多时候我们不需要那么重的实现,毕竟加锁会影响性能。故一般直接使用ArrayList,而且,一定要实现线程安全的动态数组,也轮不到用 Vector,可以使用 JUC 中的 CopyOnWriteArraylist 等容器。或者用 Collections 类的同步 List 静态方法来转换为同步List

2、Vector 的部分方法名太长,ArrayList 的对应实现方法名短些,便于阅读,目前仍在使用 Vector 的软件,基本都是为了兼容旧库和懒得改

3、Vector 的容量增长性能很差

Vector 是可变数组,初始 length 是 10 ,如果超过 length 时,会以 100% 比率增长 length,即变成20,所以存在内存浪费的现象,而 Arraylist 的 length 是以 50% 比率增加,所以相比来说,内存使用率较高

主题通知观察者的顺序很奇葩,有bug风险

看源码得知,主题通知观察者的顺序,是 tmd 的倒叙,导致通知观察者的顺序和注册的顺序不一样,如果业务代码对顺序有要求,就不好弄了

主题是类实现的,扩展难

众所周知,Java 没有多继承机制,如果具体主题除了继承主题类外, 还想继承其他业务类,就没法儿写了。典型的违背了”组合(聚合)优于继承的”设计原则。故一般自定义的实现比较多,也不难。虽然 JDK 给我们做了封装,但是很多很多时候,业务需求复杂,JDK 的 API 并不能满足我们的需求。

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

JDK 自带的观察者模式源码分析以及和自定义实现的取舍

Original: https://www.cnblogs.com/kubixuesheng/p/10360194.html
Author: dashuai的博客
Title: JDK 自带的观察者模式源码分析以及和自定义实现的取舍

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/543221/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • [水贴]GALGAME 怎么判定好感度?

    判定好感度最好就是分析剧本,我们以恋盾为例 这是文本包下载地址: http://blog.sina.com.cn/s/blog_624893280100vymp.html 我们打开…

    Java 2023年5月29日
    085
  • Spring Boot 入门(五)部署MySQL服务

    安装MySQL8.0 使用最新的包管理器安装MySQL <span class="ln-bg"><span class="ln-nu…

    Java 2023年6月5日
    087
  • Linux命令(一)

    文件列表 ls #表示查看当前目录下的文件 ls -a #表示查看当前目录下的所有文件(包含隐藏文件) ls -l #表示查看当前目录下的详细信息 ll #表示查看当前目录下的详细…

    Java 2023年6月6日
    081
  • 学生成绩管理系统【c】

    include Original: https://www.cnblogs.com/java20130723/p/3211444.htmlAuthor: 程序流程图Title: 学…

    Java 2023年5月29日
    072
  • ASP.NET Core Web API 中控制器操作的返回类型

    项目 2022/04/18 7 个参与者 ASP.NET Core 提供以下 Web API 控制器操作返回类型选项: 本文档说明每种返回类型的最佳适用情况。 特定类型 最简单的操…

    Java 2023年5月29日
    057
  • 【Java】在IDEA中将Javafx项目打包成为可运行的.jar文件

    在IDEA中将Javafx项目打包成为可运行的.jar文件。 使用JDK17.0.2。 在使用Javafx制作一个图形化界面程序的时候,我遇到了打包文件的难题。按照网上给出的解决方…

    Java 2023年6月8日
    059
  • Java日期类题目

    每类题都有各种各样解决的方式,大家随意发散 分析以下需求,并用代码实现1.已知日期字符串:”2015-10-20″,将该日期字符串转换为日期对象2.将(1)…

    Java 2023年6月5日
    078
  • 深入学习SpringBoot

    快速上手SpringBoot 1.1 SpringBoot入门程序开发 SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来 简化Spring应用的 初始搭建…

    Java 2023年6月6日
    068
  • 亿级消息中心架构方案概述【原创】

    目标 技术目标: 上行到消息队列api吞吐量10000条/秒,下发第三方平台1000条/秒(仅平台自身处理能力,第三方看第三方处理能力极限指标为准);保证消息中心100%高可用。 …

    Java 2023年6月8日
    079
  • 【碎】@Value 注解

    注意lombok和spring posted @2022-09-16 14:12 HypoPine 阅读(6 ) 评论() 编辑 Original: https://www.cnb…

    Java 2023年6月15日
    082
  • redis启动报无权限

    设置一下SELINUX 关闭SElinux 查看selinux状态 [root@localhost ~]# getenforce Enforcing 表示启动 临时关闭 [root…

    Java 2023年6月9日
    072
  • git同一仓库,不同分支融合

    命令版 &#x793A;&#x4F8B;&#xFF1A; 将main分支转到master分支上 切到需要使用的分支 git checkout master …

    Java 2023年6月16日
    065
  • 更聪明地学习,而不是苦读——《如何高效学习》

    我们可能都听过一句话: 吾生也有涯,而知也无涯。以有涯随无涯,殆已!——《庄子. 内篇. 养生主第三》 所以,需要持续大量学习的童鞋,比方说我等程序员们,除了要从知识的海洋中精挑细…

    Java 2023年6月5日
    089
  • Dos 命令

    管理员方式运行:选择以管理员方式与运行 常用的Dos命令 Original: https://www.cnblogs.com/mycode-blog/p/15365967.html…

    Java 2023年6月8日
    063
  • Python工具箱系列(五)

    上一期介绍了Anaconda的安装,本期介绍Miniconda的安装,它们共同的部分是Conda,确实如此。Conda是一个开源的包管理系统,本身的志向非常宏大,要为Python、…

    Java 2023年6月16日
    071
  • 关于soapUI调用报错:Error reading XMLStreamReader:

    不积跬步无以至千里,记录每一个小bug解决过程: 问题描述: 今天是第一次使用webservice,也是第一次调webservice,用soapUI调用时,产生报错 先是报错Err…

    Java 2023年6月16日
    0112
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球