已阅读5页,还剩2页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
探索设计模式 星际争霸探险之旅 6 6 6 6 单例单例模式 模式 SingletonSingletonSingletonSingleton PatternPatternPatternPattern 前面说提到的五种创建模式 主要解决的问题是如何创建对象 获得产品 而单例模式最要 关心的则是对象创建的次数以及何时被创建 Singleton 模式可以是很简单的 它的全部只需要一个类就可以完成 看看这章可怜的 UML 图 但是如果在 对象创建的次数以及何时被创建 这两点上较真起来 Singleton 模式可以相 当的复杂 比头五种模式加起来还复杂 譬如涉及到 DCL 双锁检测 double checked locking 的 讨论 涉及到多个类加载器 ClassLoader 协同时 涉及到跨 JVM 集群 远程 EJB 等 时 涉 及到单例对象被销毁后重建等 对于复杂的情况 本章中会涉及到其中一些1 目的 目的 希望对象只创建一个实例 并且提供一个全局的访问点 场景 场景 Kerrigan 对于 Zerg 来说是个至关重要的灵魂人物 无数的 Drone Zergling Hydralisk 可 以被创造 被牺牲 但是 Kerrigan 得存在关系到 Zerg 在这局游戏中的生存 而且 Kerrigan 是不允 许被多次创造的 必须有且只有一个虫族刀锋女王的实例存在 这不是游戏规则 但这是个政治 问题 分析 分析 如前面一样 我们还是尝试使用代码来描述访问 Kerrigan 的过程 看看下面的 UML 图 简 单得我都不怎么好意思放上来占版面 getInstance SingletonKerriganSingletonKerriganSingletonKerriganSingletonKerrigan 图 6 1 单例模式的 UML 图 结构是简单的 只是我们还有一些小小的要求如下 1 最基本要求 每次从 getInstance 都能返回一个且唯一的一个 Kerrigan 对象 2 稍微高一点的要求 Kerrigan 很忙 很多人找 所以希望这个方法能适应多线程并发访问 3 再提高一点的要求 Zerg 是讲究公务员效率的社会 希望找 Kerrigan 的方法性能尽可能高 4 最后一点要求是 Kerrigan 自己提出的 体谅到 Kerrigan 太累 希望多些睡觉时间 因此 Kerrigan 希望实现懒加载 Lazy Load 在需要的时候才被构造 5 原本打算说还提要处理多 ClassLoader 多 JVM 等情况 不过还是不要把情况考虑的太复杂 了 暂且先放过作者吧 探索设计模式 星际争霸探险之旅 我们第一次写的单例模式是下面这个样子的 这个写法我们把四点需求从上往下检测 发现第二点的时候就出了问题 假设这样的场景 两个线程并发调用 SingletonKerriganA getInstance 假设线程一先判断完 instance 是否为 null 既 代码中的 line A 进入到 line B 的位置 刚刚判断完毕后 JVM 将 CPU 资源切换给线程二 由于 线程一还没执行 line B 所以 instance 仍然是空的 因此线程二执行了 new SignletonKerriganA 操作 片刻之后 线程一被重新唤醒 它执行的仍然是 new SignletonKerriganA 操作 好了 问 题来了 两个 Kerrigan 谁是李逵谁是李鬼 紧接着 我们做单例模式的第二次尝试 实现单例访问Kerrigan的第一次尝试 publicpublicpublicpublic classclassclassclass SingletonKerriganA 单例对象实例 privateprivateprivateprivate staticstaticstaticstatic SingletonKerriganAinstance nullnullnullnull publicpublicpublicpublic staticstaticstaticstatic SingletonKerriganA getInstance ifififif instance nullnullnullnull line A instance newnewnewnew SingletonKerriganA line B returnreturnreturnreturninstance 实现单例访问Kerrigan的第二次尝试 publicpublicpublicpublic classclassclassclass SingletonKerriganB 单例对象实例 privateprivateprivateprivate staticstaticstaticstatic SingletonKerriganBinstance nullnullnullnull publicpublicpublicpublic synchronizedsynchronizedsynchronizedsynchronized staticstaticstaticstatic SingletonKerriganB getInstance ifififif instance nullnullnullnull instance newnewnewnew SingletonKerriganB returnreturnreturnreturninstance 探索设计模式 星际争霸探险之旅 比起第一段代码仅仅在方法中多了一个 synchronized 修饰符 现在可以保证不会出线程问题 了 但是这里有个很大 至少耗时比例上很大 的性能问题 除了第一次调用时是执行了 SingletonKerriganB 的构造函数之外 以后的每一次调用都是直接返回 instance 对象 返回对象这 个操作耗时是很小的 绝大部分的耗时都用在 synchronized 修饰符的同步准备上 因此从性能上 说很不划算 那继续把代码改成下面的样子 基本上 把 synchronized 移动到代码内部是没有什么意义的 每次调用 getInstance 还是要进 行同步 同步本身没有问题 但是我们只希望在第一次创建 Kerrigan 实例的时候进行同步 因此 我们有了下面的写法 双重锁定检查 DCL 实现单例访问Kerrigan的第三次尝试 publicpublicpublicpublic classclassclassclass SingletonKerriganC 单例对象实例 privateprivateprivateprivate staticstaticstaticstatic SingletonKerriganCinstance nullnullnullnull publicpublicpublicpublic staticstaticstaticstatic SingletonKerriganC getInstance synchronizedsynchronizedsynchronizedsynchronized SingletonKerriganC classclassclassclass ifififif instance nullnullnullnull instance newnewnewnew SingletonKerriganC returnreturnreturnreturninstance 实现单例访问Kerrigan的第四次尝试 publicpublicpublicpublic classclassclassclass SingletonKerriganD 单例对象实例 privateprivateprivateprivate staticstaticstaticstatic SingletonKerriganDinstance nullnullnullnull publicpublicpublicpublic staticstaticstaticstatic SingletonKerriganD getInstance ifififif instance nullnullnullnull synchronizedsynchronizedsynchronizedsynchronized SingletonKerriganD classclassclassclass ifififif instance nullnullnullnull 探索设计模式 星际争霸探险之旅 看起来这样已经达到了我们的要求 除了第一次创建对象之外 其他的访问在第一个 if 中就 返回了 因此不会走到同步块中 已经完美了吗 我们来看看这个场景 假设线程一执行到 instance new SingletonKerriganD 这句 这里看起 来是一句话 但实际上它并不是一个原子操作 原子操作的意思就是这条语句要么就被执行完 要么就没有被执行过 不能出现执行了一半这种情形 事实上高级语言里面非原子操作有很多 我们只要看看这句话被编译后在JVM执行的对应汇编代码就发现 这句话被编译成8条汇编指令 大致做了 3 件事情 1 给 Kerrigan 的实例分配内存 2 初始化 Kerrigan 的构造器 3 将 instance 对象指向分配的内存空间 注意到这步 instance 就非 null了 但是 由于 Java 编译器允许处理器乱序执行 out of order 以及 JDK1 5 之前 JMM Java Memory Medel 中 Cache 寄存器到主内存回写顺序的规定 上面的第二点和第三点的顺序是无 法保证的 也就是说 执行顺序可能是 1 2 3 也可能是 1 3 2 如果是后者 并且在 3执行完毕 2 未执行之前 被切换到线程二上 这时候 instance 因为已经在线程一内执行过了第三点 instance 已经是非空了 所以线程二直接拿走 instance 然后使用 然后顺理成章地报错 而且这种难以 跟踪难以重现的错误估计调试上一星期都未必能找得出来 真是一茶几的杯具啊 DCL 的写法来实现单例是很多技术书 教科书 包括基于 JDK1 4 以前版本的书籍 上推荐 的写法 实际上是不完全正确的 的确在一些语言 譬如 C 语言 上 DCL 是可行的 取决于是 否能保证 2 3 步的顺序 在JDK1 5 之后 官方已经注意到这种问题 因此调整了 JMM 具体化 了volatile关键字 因此如果JDK是1 5或之后的版本 只需要将 instance的定义改成 private volatile static SingletonKerriganD instance null 就可以保证每次都去 instance 都从主内存读取 就可以 使用 DCL 的写法来完成单例模式 当然 volatile 或多或少也会影响到性能 最重要的是我们还要 考虑 JDK1 42 以及之前的版本 所以本文中单例模式写法的改进还在继续 代码倒越来越复杂了 现在先来个返璞归真 根据 JLS Java Language Specification 中的规 定 一个类在一个 ClassLoader 中只会被初始化一次 这点是 JVM 本身保证的 那就把初始化实 例的事情扔给 JVM 好了 代码被改成这样 instance newnewnewnew SingletonKerriganD returnreturnreturnreturninstance 实现单例访问Kerrigan的第五次尝试 publicpublicpublicpublic classclassclassclass SingletonKerriganE 单例对象实例 探索设计模式 星际争霸探险之旅 好吧 如果这种写法是完美的话 那前面那么几大段话就是作者在消遣各位读者 这种写法 不会出现并发问题 但是它是饿汉式的 在 ClassLoader 加载类后 Kerrigan 的实例就会第一时间 被创建 饿汉式的创建方式在一些场景中将无法使用 譬如 Kerrigan 实例的创建是依赖参数或者 配置文件的 在 getInstance 之前必须调用某个方法设置参数给它 那样这种单例写法就无法使用 了 再来看看下面这种我觉得能应对较多场景的单例写法 这种写法仍然使用 JVM 本身机制保证了线程安全问题 由于 SingletonHolder 是私有的 除了 getInstance 之外没有办法访问它 因此它是懒汉式的 同时读取实例的时候不会进行同步 没有 性能缺陷 也不依赖 JDK 版本 其他单例模式的写法还有很多 如使用本地线程 ThreadLocal 来处理并发以及保证一个线 程内一个单例的实现 GoF 原始例子中使用注册方式应对单例类需要需要继承时的实现 使用指 定类加载器去应对多 ClassLoader 环境下的实现等等 我们做开发设计工作的时 应当既要考虑既要考虑 到需求可能出现的扩展与变化 也应当避免到需求可能出现的扩展与变化 也应当避免 幻影需求幻影需求 导致无谓的提升设计 实现复杂度 导致无谓的提升设计 实现复杂度 最最 终反而带来工期 性能和稳定性的损失 设计不足与设计过度都是危害 所以说没有最好的单终反而带来工期 性能和稳定性的损失 设计不足与设计过度都是危害 所以说没有最好的单例例 模式 只有最合适的单例模式 模式 只有最合适的单例模式 到这里为止 单例模式本身就先告一段落了 最后在介绍从其他途径屏蔽构造单例对象的方 法 privateprivateprivateprivate staticstaticstaticstatic SingletonKerriganEinstance newnewnewnew SingletonKerriganE publicpublicpublicpublic staticstaticstaticstatic SingletonKerriganE getInstance returnreturnreturnreturninstance 实现单例访问Kerrigan的第六次尝试 publicpublicpublicpublic classclassclassclass SingletonKerriganF privateprivateprivateprivate staticstaticstaticstatic classclassclassclass SingletonHolder 单例对象实例 staticstaticstaticstatic finalfinalfinalfinal SingletonKerriganFINSTANCE newnewnewnew SingletonKerriganF publicpublicpublicpublic staticstaticstaticstatic SingletonKerriganF getInstance returnreturnreturnreturn SingletonHolder INSTANCE 探索设计模式 星际争霸探险之旅 1 直接 new 单例对象 2 通过反射构造单例对象 3 通过序列化构造单例对象 对于第一种情况 一般我们会加入一个 private 或者 protected 的构造函数 这样系统就不会自 动添加那个 public 的构造函数了 因此只能调用里面的 static 方法 无法通过 new 创建对象 对于第二种情况 反射时可以使用 setAccessible 方法来突破 private 的限制 我们需要做到第 一点工作的同时 还需要在在 ReflectPermission suppressAccessChecks 权限下使用安全管理器 SecurityManager 的 checkPermission 方法来限制这种突破 一般来说 不会真的去做这些事情 都是通过应用服务器进行后台配置实现 对于第三种情况 如果单例对象有必要实现 Serializable 接口 很少出现 则应当同时实现 readResolve 方法来保证反序列化的时候得到原来的对象 基于上述情况 将单例模式增加两个方法 能应对大多数情况的单例实现 publicpublicpublicpublic classclassclassclass SingletonKerrigan implementsimplementsimplementsimplements Serializable privateprivateprivateprivate staticstaticstaticstatic classclassclassclass SingletonHolder 单例对象实例 stat
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年中国铝合金升降机专用泵站行业市场前景预测及投资价值评估分析报告
- 2026年中国大型机床液压站行业市场前景预测及投资价值评估分析报告
- 2025年六安霍山县消防救援局招聘政府专职消防员9人考试笔试参考题库附答案解析
- 2025辽宁农业职业技术学院面向社会招聘高层次人才2人(第三批)笔试考试参考试题及答案解析
- 2025浙江宁波市北仑区交通投资集团有限公司招聘矿山专职技术人员6人笔试考试参考题库及答案解析
- 2025年11月广东广州市天河区童时光幼儿园招聘编外聘用制专任教师1人考试笔试模拟试题及答案解析
- 《电网安全风险管控办法》
- B端产品经理职业规划
- 外科股骨骨折术后康复教程
- 感染科院内感染管理要点
- 新药靶点的毒理学研究
- 仓库搬运人员安全注意事项培训课件
- 降低crrt非计划下机率成果报告书
- 档案整理及数字化服务方案
- 猫武士三部曲 4天蚀遮月
- 《内河航道引航》考试复习(重点)题库(300题)
- 工程整改通知单问题整改通知单
- 降低手术患者术中低体温发生率
- 大班语言-蜂蜜失窃谜案
- 中国地质大学地球科学概论教学课程pptpart5公开课获奖课件
- YY/T 0801.1-2010医用气体管道系统终端第1部分:用于压缩医用气体和真空的终端
评论
0/150
提交评论