设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
20291 人已学习
课程目录
已更新 50 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (12讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
20 | 理论六:我为何说KISS、YAGNI原则看似简单,却经常被用错?
21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?
22 | 理论八:如何用迪米特法则(LOD)实现“高内聚、松耦合”?
23 | 实战一(上):针对业务系统的开发,如何做需求分析和设计?
24 | 实战一(下):如何实现一个遵从设计原则的积分兑换系统?
25 | 实战二(上):针对非业务的通用框架开发,如何做需求分析和设计?
26 | 实战二(下):如何实现一个支持各种统计规则的性能计数器?
设计原则与思想:规范与重构 (11讲)
27 | 理论一:什么情况下要重构?到底重构什么?又该如何重构?
28 | 理论二:为了保证重构不出错,有哪些非常能落地的技术手段?
29 | 理论三:什么是代码的可测试性?如何写出可测试性好的代码?
30 | 理论四:如何通过封装、抽象、模块化、中间层等解耦代码?
31 | 理论五:让你最快速地改善代码质量的20条编程规范(上)
32 | 理论五:让你最快速地改善代码质量的20条编程规范(中)
33 | 理论五:让你最快速地改善代码质量的20条编程规范(下)
34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题
35 | 实战一(下):手把手带你将ID生成器代码从“能用”重构为“好用”
36 | 实战二(上):程序出错该返回啥?NULL、异常、错误码、空对象?
37 | 实战二(下):重构ID生成器项目中各函数的异常处理代码
设计原则与思想:总结课 (3讲)
38 | 总结回顾面向对象、设计原则、编程规范、重构技巧等知识点
39 | 运用学过的设计原则和思想完善之前讲的性能计数器项目(上)
40 | 运用学过的设计原则和思想完善之前讲的性能计数器项目(下)
设计模式与范式:创建型 (6讲)
41 | 单例模式(上):为什么说支持懒加载的双重检测不比饿汉式更优?
42 | 单例模式(中):我为什么不推荐使用单例模式?又有何替代方案?
43 | 单例模式(下):如何设计实现一个集群环境下的分布式单例模式?
44 | 工厂模式(上):我为什么说没事不要随便用工厂模式创建对象?
45 | 工厂模式(下):如何设计实现一个Dependency Injection框架?
46 | 建造者模式:详解构造函数、set方法、建造者模式三种对象创建方式
不定期加餐 (3讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
春节特别加餐 | 王争:如何学习《设计模式之美》专栏?
免费
设计模式之美
登录|注册

43 | 单例模式(下):如何设计实现一个集群环境下的分布式单例模式?

王争 2020-02-10
上两节课中,我们针对单例模式,讲解了单例的应用场景、几种常见的代码实现和存在的问题,并粗略给出了替换单例模式的方法,比如工厂模式、IOC 容器。今天,我们再进一步扩展延伸一下,一块讨论一下下面这几个问题:
如何理解单例模式中的唯一性?
如何实现线程唯一的单例?
如何实现集群环境下的单例?
如何实现一个多例模式?
今天的内容稍微有点“烧脑”,希望你在看的过程中多思考一下。话不多说,让我们正式开始今天的学习吧!

如何理解单例模式中的唯一性?

首先,我们重新看一下单例的定义:“一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。”
定义中提到,“一个类只允许创建唯一一个对象”。那对象的唯一性的作用范围是什么呢?是指线程内只允许创建一个对象,还是指进程内只允许创建一个对象?答案是后者,也就是说,单例模式创建的对象是进程唯一的。这里有点不好理解,我来详细地解释一下。
我们编写的代码,通过编译、链接,组织在一起,就构成了一个操作系统可以执行的文件,也就是我们平时所说的“可执行文件”(比如 Windows 下的 exe 文件)。可执行文件实际上就是代码被翻译成操作系统可理解的一组指令,你完全可以简单地理解为就是代码本身。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(26)

  • 小晏子
    要回答这个课后问题,要理解classloader和JDK8中使用的双亲委派模型。
    classloader有两个作用:1. 用于将class文件加载到JVM中;2. 确认每个类应该由哪个类加载器加载,并且也用于判断JVM运行时的两个类是否相等。
    双亲委派模型的原理是当一个类加载器接收到类加载请求时,首先会请求其父类加载器加载,每一层都是如此,当父类加载器无法找到这个类时(根据类的全限定名称),子类加载器才会尝试自己去加载。
    所以双亲委派模型解决了类重复加载的问题, 比如可以试想没有双亲委派模型时,如果用户自己写了一个全限定名为java.lang.Object的类,并用自己的类加载器去加载,同时BootstrapClassLoader加载了rt.jar包中的JDK本身的java.lang.Object,这样内存中就存在两份Object类了,此时就会出现很多问题,例如根据全限定名无法定位到具体的类。有了双亲委派模型后,所有的类加载操作都会优先委派给父类加载器,这样一来,即使用户自定义了一个java.lang.Object,但由于BootstrapClassLoader已经检测到自己加载了这个类,用户自定义的类加载器就不会再重复加载了。所以,双亲委派模型能够保证类在内存中的唯一性。
    联系到课后的问题,所以用户定义了单例类,这样JDK使用双亲委派模型加载一次之后就不会重复加载了,保证了单例类的进程内的唯一性,也可以认为是classloader内的唯一性。当然,如果没有双亲委派模型,那么多个classloader就会有多个实例,无法保证唯一性。
    2020-02-10
    19
  • 下雨天
    课堂讨论
         Java中,两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
         单例类对象的唯一性前提也必须保证该类被同一个类加载器加载!
    2020-02-10
    4
    14
  • Ken张云忠
    实际上,对于 Java 语言来说,单例类对象的唯一性的作用范围并非进程,而是类加载器(Class Loader),你能自己研究并解释一下为什么吗?
    因为Jvm中类加载时采用的是双亲委派模式,对于类的唯一性的确定是通过类全名和类加载器实例一起来实现的,jdk8可以支持多个Java应用共享jre下的很多类实例就是通过扩展类加载器实现的,所以这里所说单例类实例唯一性的作用范围是类加载器指的就是即使类全名相同的类文件也必须要保证被同个类应用类加载器加载。
    2020-02-10
    3
    6
  • aoe
    老师讲的多例模式应该就是享元模式,常量池、数据库连接池经常使用。
    2020-02-10
    2
  • 大牛凯
    对于集群下的单例实现加锁有点迷惑,对象第一次实例化之后再通过getInstance就不会加锁了直接返回实例,由此有两个问题不太明白:
    1. 这时如果多个进程都拿到了这个实例,save操作需要做并发控制吗?(还是就是synchronize就行了?对java不是很熟悉)
    2. 这时该进程没有锁,但是freeInstance会释放一把锁,会有重复释放锁的问题吗?
    2020-02-10
    2
    2
  • 小喵喵
    IdGenerator.freeInstance(); 应该是idGenerator.freeInstance();
    2020-02-14
    1
  • 唐龙
    不是很懂为什么需要多例模式,什么情况下需要用到多例模式。
    2020-02-10
    1
    1
  • Snway
    对于类加载器,可以简单理解:不同类加载器之间命名空间不一样,不同类加载器加载出来的类实例是不一样的,所以如果使用多个类加载器,可能会导致单例失效而产生多个实例
    2020-02-10
    1
  • 黄林晴
    打卡
    2020-02-10
    1
  • 深入理解JAVA虚拟机第三版 总结:
    大前提:每一个类加载器,都有一个独立的类名称空间(通俗的解释:两个类只有在同一个类加载器加载的前提下,才能比较它们是否"相等")

    启动类加载器:加载JAVA_HOME\lib目录下的类库

    扩展类加载器:加载JAVA_HOME\lib\ext目录下的类库,是java SE 扩展功能, jdk9 被模块化的天然扩展能力所取代

    应用程序加载器:加载用户的应用程序

    用户自定义的加载器:供用户扩展使用,加载用户想要的内容

      这个类加载器的层次关系被称为类的"双亲委派模型"

    双亲委派模型工作流程:
     如果一个类加载器收到了加载请求,那么他会把这个请求委派给父类去完成,每一层都是如此,所以他最后会被委派到启动类加载器中,只有父类反馈自己无法完成这个加载请求时,子类才会尝试自己去加载
    类不会重复的原因:
     比如一个类,java.lang.Object,存放在JAVA_HOME/lib/rt.jar中,无论哪个类加载器想要加载他,最终都会被委派给启动类加载器去加载
    反之,如果没有双亲委派机制,用户自己编写一个java.lang.Object类,那么如果他被其他类加载器加载,内存中就会出现两个ava.lang.Object类
    2020-02-17
  • 桂城老托尼
    感谢分享
    1,线程唯一性一节,threadlocal也好,记录线程id的方式也好,如果是线程池的话,需要确认下是否需要clean。另外如果线程能被销毁,再创建的线程id是否会重复?如果重复在某些场景下可能会有问题。
    2,分布式环境下的单例一节,针对id生成器这种频繁访问的服务,如果频繁加锁效率比较低,可以考虑常用的sequnce方案,每个进程持有一段id range,保证每个(分布式)进程某时间区间不重复即可。
    3,尝试回答下课堂讨论,不同的classloader实例加载的class天然不属于一个,new出来的对象应该也不是一个,classloader是类的隔离级别。



    2020-02-16
  • 守拙
    课堂讨论:

    简单了解了下classLoader机制.
    单例模式理论上应该是ApplicationClassLoader内的单例.
    由JVM的双亲委派模型保证了类不会被重复的加载.
    2020-02-15
  • FIGNT
    类加载时采用双亲委派的机制,优先级排序启动加载器>扩展加载器>应用加载器>自定义加载器。这种机制保证相同的类只能加载一次,而且已java类库的类优先加载,而自定义的后加载。比如自己实现一个String类,类库中也有String类,加载哪个呢?有个优先级,只有高优先级没有加载时低优先级的才能加载。从这个角度看其实单例的唯一性作用于ClassLoader。只不过双亲委派机制保证只有一个类加载器加载。如果没有双亲委派机制,那么实例在类加载器中唯一,在类加载器间不唯一。所以准确说单例类对象的唯一性的作用范围并非进程,而是类加载器。
    2020-02-15
  • L🚲🐱
    不同的类加载器类全限定名不一样, 全限定名不一样就不是一个类, 而 jdk 的双亲委派模型会先加载父类, 如果父类没有, 则加载自定义类, 这样才能保证单例
    2020-02-14
  • 每天晒白牙
    看到留言大家都在讨论类加载器知识点,这是我整理的类加载器知识点,大家可以参考下https://mp.weixin.qq.com/s/KseFpfXaaGDGoUdqJNzg8w
    2020-02-14
  • 杨杰
    在多例的伪代码里面:
     public BackendServer getInstance(long serverNo) {
         return serverInstances.get(serverNo);
     }
    是不是应该改成:
     public static BackendServer getInstance(long serverNo) {
         return serverInstances.get(serverNo);
     }
    2020-02-12
  • hanazawakana
    ThreadLocal底层是基于ThreadLocalMap,他有一个Entry[] table,好像并不是基于HashMap?
    2020-02-12
  • Algo
    如果这个类已经被加载,则不会重复加载。如加载A类则也要确保其父类已经被加载完成。且不同的类在JVM中有不同的加载器。
    2020-02-12
  • 小刀
    不同类加载器之间命名空间不一样,不同类加载器加载出来的类实例是不一样的,所以如果使用多个类加载器,可能会导致单例失效而产生多个实例
    2020-02-11
  • 辣么大
    今天学到的各种单例,真是打开眼界。其中学到了双亲指派和“类加载”这两个知识点我不熟悉,接下来要研究一下。
    2020-02-10
收起评论
26
返回
顶部