Java设计模式之单例模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样(百度百科)。
设计模式分类:
创建型模式:
单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
结构型模式:
适配器模式、桥接模式、装饰模式、组合模式、外观模式、代理模式
行为模式:
责任链模式、命令模式、策略模式、观察模式、解释器模式、迭代模式、中介模式、备忘录模式、状态模式、模板模式、访问者模式
设计原则
开闭原则: 一个软件实体应该对扩展开放 对修改闭合。
里氏代换原则: 任何能使用父类的地方一定能使用子类。
依赖倒转原则: 要依赖于抽象 不要依赖于实现。或者是抽象不应该依赖与细节,细节应该依赖于抽象。
合成聚合复用原则: 尽量使用合成聚合而不是继承去实现复用。
迪米特法则: 一个软件实体应该尽可能少的与其它实体发生相互作用。
接口隔离原则: 应当为客户提供尽可能小的单独的接口 而不应该提供大的综合性的接口。
今天自己学习了一下单例模式。
单例模式的核心作用
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。意味着只有一个存在的对象。
单例的优点:
只生成一个实例,减少系统开销,提高系统性能。
提供一个全局的访问点,资源共享。
单例的优点:
扩展性比较差。
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
常见运用场景:
代码中经常使用的读取配置文件的类,一般我们只会有一个对象。没有必要每次读取一个对象都创建新对象。
Windows的任务管理器,怎么按都会只有一个出来。
日志对象,我们一般也会使用单例。
常见的5种单例模式实现方式:
主要:
饿汉式(线程安全、调用效率高。不能延时加载 以空间换时间)。
懒汉式(线程安全、调用效率不高,可以延时加载 以时间换空间)。
其他:
双重检测锁式(JVM内存的“无序写入”问题 不建议使用)
静态内部类式(线程安全、调用效率高,延时加载)
枚举单例(线程安全、调用效率高,不能延时加载)
懒汉式单例:
package com.roc.singlet; /** * 懒汉式单例 * @author liaowp * */ public class SingletPatter1 { //类初始化时,立即加载这个对象。加载类时,线程安全。 private static SingletPatter1 instance = new SingletPatter1(); private SingletPatter1(){ } public static SingletPatter1 getInstance(){ return instance; } }
懒汉式:
package com.roc.singlet; /** * 懒汉式(线程安全、调用效率不高,可以延时加载 以时间换空间,对象延迟加载)。 * @author liaowp * */ public class SingletPatter2 { //初始化时没有立即创建对象。 private static SingletPatter2 instance; //私有话构造器 private SingletPatter2(){ } public static synchronized SingletPatter2 getInstance(){ if(instance==null){ instance = new SingletPatter2(); } return instance; } }
主要特点:懒加载(延时加载)使用的时候才加载。
存在问题:每次都要同步,并发效率低。
怎么理解2种类型的单例:
饿汉:想一想你饿的时候马上就想去吃东西,立即就加载,这就是饿汉。
懒汉:即时自己很饿了,但是很懒。我想要吃的时才去吃。使用的时候才加载。这是懒汉。
双重检测锁实现:
1 package com.roc.singlet; 2 3 /** 4 5 * 双重检测锁实现 6 7 * @author liaowp 8 9 * 10 11 */ 12 13 public class SingletPatter3 { 14 15 public static SingletPatter3 instance = null; 16 17 18 19 //每个模式将同步内容下发到if内部,提高执行效率。只有第一次才同步,创建以后就没有必要了 20 21 public static SingletPatter3 getInstance(){ 22 23 if(instance==null){ 24 25 SingletPatter3 singletPatter3; 26 27 synchronized (SingletPatter3.class) { 28 29 singletPatter3 = instance; 30 31 if(singletPatter3==null){ 32 33 synchronized (SingletPatter3.class) { 34 35 if(singletPatter3==null){ 36 37 singletPatter3 = new SingletPatter3(); 38 39 } 40 41 } 42 43 instance = singletPatter3; 44 45 } 46 47 } 48 49 } 50 51 return instance; 52 53 } 54 55 56 57 private SingletPatter3(){ 58 59 60 61 } 62 63 }
存在问题:由于编译器优化原因和JVM底层内部模型原因。偶尔会出现问题。
静态内部类的实现方式:
package com.roc.singlet; /** * 静态内部类实现方式 * @author liaowp * */ public class SingletPatter4 { private static class SingletPatterInstance{ private static SingletPatter4 instance = new SingletPatter4(); } public static SingletPatter4 getInstance(){//使用到才去调用内部类加载方式,即懒加载 return SingletPatterInstance.instance; } public SingletPatter4(){ } } 优点: 外部没有static修饰,不会立即加载。 Instance的是final static的类型,保证了内存中只有一个实例。线程安全。 兼备并发高效的调用和延迟加载的优势。 枚举实现方式 package com.roc.singlet; /** * 枚举方式实现单例 * @author liaowp * */ public enum SingletPatter5 { INSTANCE; }
如何选择:
单例对象,占用资源少,不需要延时加载的时候可以选择枚举式与饿汉式。
枚举式 》 饿汉式。
单例对象 占用资源大 需要延时加载:静态内部类式 懒汉式
静态内部类式 》 懒汉式