戏说设计模式 - 只有一个中国 - 单例模式
Posted java新人交流
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了戏说设计模式 - 只有一个中国 - 单例模式相关的知识,希望对你有一定的参考价值。
戏说设计模式 - 单例模式
[toc]
简介
Singleton
保证类有且仅有一个实例,并提供一个访问它的全局站点
类型
饿汉模式
懒汉模式
懒汉模式 PK 饿汉模式
/ | 懒汉模式 | 饿汉模式 | 备注 |
---|---|---|---|
线程安全 | 不安全 | 安全 | |
类加载速度 | 快 | 慢 | 饿汉模式在类加载时就初始化其唯一的实例而懒汉模式不需要,所以饿汉模式在类加载时速度会慢一点 |
运行速度 | 慢 | 快 | 饿汉模式在类加载时就已经完成了其唯一实例的初始化。而懒汉模式还需要进行初始化操作,所以懒汉模式在运行时速度会慢一点 |
优缺点
优点
由于单例模式在内存中只有一个实例,
减少了内存开支
,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。由于单例模式只生成一个实例,所以
减少了系统的性能开销
,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。单例模式可以
避免对资源的多重占用
,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。单例模式可以在系统设置全局的访问点,
优化和共享资源访问
,例如可以设计一个单例类,负责所有数据表的映射处理。缺点
单例模式一般没有接口,
扩展很困难
,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。单例模式
对测试是不利的
。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。单例模式
与单一职责原则有冲突
。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。
适用场景
应用程序中的该对象有且只有一个即可
创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
注意事项
在高并发情况下,请注意单例模式的线程同步问题。
案例
只有一个中国
小鸟:“该死的日本鬼子,...”
大牛:“小鸟,你突然骂日本人干啥?”
小鸟:“连一个怀孕六个月的孕妇都不放过,居然将刺刀插进孕妇的腹部,挑出一个还没出世的孩子,好好的一个南京城,硬是被他们弄的血流成河.”
大牛:“你在看《南京大屠杀》吧!在那个贫穷积弱的年代,又何止是一个南京,整个华夏大地都被他们弄的乌烟瘴气,幸好,咱们的前辈们站起来了,打赢了那一场硬仗。”
小鸟:“是啊!胜利来之不易,所以我们要坚持只有一个中国的原则。到现在了,居然还有人闹藏独,台独,真想化身成欧特曼,钢铁侠,谁敢不服,谁敢闹分裂,就揍谁!”
大牛:“哈哈,想不到我们小鸟还是一个这么有血性的人,不过大象是不会去计较蚂蚁的挑衅的。”
大牛:“香港回归,你能用程序实现吗?”
小鸟:“我编写一个中国类,定义一个城市返回的方法,这里定义为带参的(参数:城市),因为我们都知道,香港回归后,有澳门回归,我更期待不久的将来,由台湾回归.这是事实的角度出发,同时也是从系统的扩展性出发”
public class China { public void returnChina(String city) { System.out.println(city+"回归"); } }
小鸟:“客户端调用,香港回归”
China china = new China(); china.returnChina("香港");
大牛:“小鸟,说的不错,扩展性你已经考虑到了,但是就像你前面说的胜利来之不易,我们要坚持只有一个中国的原则,而你这里有体现没?”
小鸟:“没有,我的中国类是new出来的,如果我不new的话,我就没法创建对象了啊”
大牛:“你可以将它的构造方法私有化啊。”
懒汉模式实现
私有化构造方法
:限制产生多个对象
小鸟:“构造方法私有化
,是将构造方法的修饰符改成private吗?”
private China() { }
大牛:“是的。”
创建类的唯一实例
:创建中国类的唯一实例,并使用private static 修饰
小鸟:“这样改了,我外部就没法实例化了哦,不要说台湾回归,就是已经回归了的香港,澳门,都回归不了啊?”
大牛:“private的作用域是?”
小鸟:“在其类的内部是可以使用的,我明白了,你是说让我在中国类里面声明一个自己的对象,然后通过get方法让它自行实例化.同时也是为了提供给外部使用”
大牛:“不错不错,一点就通,不过能将代码写出来,才表示真正理解了哦!”
小鸟:“这还不容易”
单例对象:China
类中其它方法尽量是static
public class China { // 自行实例化 private static China china; private China() { } // 返回我自己的对象 public static China getChina() { if (china == null) { china = new China(); } return china; } public void returnChina(String city) { System.out.println(city + "回归"); } }
客户端
China china = China.getChina(); china.returnChina("香港");
大牛:“不错,这里还有一点需要知道的就是,类中的其它方法,尽量是static
”
小鸟:“ok,我马上把returnChina改成static修饰.”
饿汉模式实现
大牛:“这里还有一个问题需要说明一下,就是关于并发
,会导致线程不安全。”
小鸟:“并发?”
大牛:“换句话说吧!假设我们的香港和澳门是同一天同一时刻回归的,那么也就代表会同时去访问China类 ---> 由于开始并没有任何城市访问过China类,所以会导致它们都进入getChina()方法中的if语句里面 ---> 这样的话就会造成我们的china会new两次。”
小鸟:“我明白了,我们这个单例模式,就是为了保证整个应用程序中有且只有一个该对象,像开始那种低并发的情况,我那样写是没什么问题的,但是如果要考虑高并发的话,就不能这么写了,如果不这么写,我又该如何写呢?”
大牛:“不错我们的小鸟还挺善于总结的,关于并发导致线程不安全,我们这里有几种解决方案。”
第1种:synchronized
在getChina方法前加synchronize关键字
synchronize作用:用于解决线程同步的问题, eg:A,B同时访问方法c, 如果不加synchronize,则同时访问,如果加了B会等A访问完在进行访问
大牛:“第1种,就是在getChina方法前加synchronized,它的作用就是香港,澳门同一时刻回归时,它们种任意一个获取完中国对象,另一个才会去获取对象,也就解决了会同时产生两个中国对象的问题.”
public static synchronized China getChina() { if (china == null) { china = new China(); } return china; }
第2种:双重锁定
Double-Check Locking
小鸟:“如果是这样的话,确实是能解决同时产生两个中国对象的问题,但是每次获取China对象时,都会进行锁定,这样不好吧!会影响性能吧?”
大牛:“确实,分析的不错,所以这里我就给你讲一下第2种实现方式:双重锁定,具体代码如下:”
public static China getChina() { if (china == null) { synchronized (new Object()) { if (china == null) { china = new China(); } } } return china; }
大牛:“我们这里在加锁前面进行了一次判断,所以就解决了我们每次都要进行加锁操作的问题,我们再加锁的代码块中再次进行判断,就解决了我们会产生两个对象的问题.”
第3种:饿汉模式
小鸟:“如果是这样的话,确实是能解决同时产生两个中国对象的问题,但是我们假设当前第1个回归的是香港,香港正在获取中国这个对象,但出了意外,大不列颠及北爱尔兰联合王国政府欠揍,突然反悔,那么我们的香港就会一直处于查找中国的阶段,由于我们加了synchronized关键字,它会解决并发的问题,对线程进行一个排序,从而也就导致我们的香港如果不回归的话,我们的澳门也就不能回归了,这样是不是太亏了啊!”
大牛:“分析的不错,确实会有这个问题,所以我们这里推荐第3种实现方式,饿汉模式,即在类进行装载的时候,就对其进行实例化.”
public class China { // 自行实例化 private static final China china = new China(); private China() { } // 返回我自己的对象 public static China getChina() { return china; } // 类中其它方法,建议用static修饰 public static void returnChina(String city) { System.out.println(city + "回归"); } }
小鸟:“这样就完美了!”
大牛:“非也,非也,我们这3种方式都是从线程安全的角度来考虑,第1种:线程安全,但是严重影响性能;第2种:线程安全,性能优于第1种;第3种:线程安全,性能优于前面两种,但是由于类装载的时候,就进行了唯一实例的初始化,会导致内存的浪费。”
小鸟:“那我采用哪种比较优呢?”
大牛:“我们一般是采用第3种,内存浪费点就浪费点,现在设备都不差这点内存,最主要的是运行时,由于一开始已经加载,所以运行速度会快一点。”
以上是关于戏说设计模式 - 只有一个中国 - 单例模式的主要内容,如果未能解决你的问题,请参考以下文章