一、设计模式
今天开始学习总结设计模式,首先GOF23种设计模式是国外“F4”总结的“经验之谈”,它其实是一种思想,用以在实际开发中对项目构建的一种参考,在一定程度上使用设计模式能够对程序有很大的提升,提升程序的性能、扩展性、可维护行等等,但是也要避免陷入“过度设计”的局面,这样就适得其反了,所以掌握好真正的精髓和思想就尤为重要了。
二、单例模式
都说单例模式是设计模式中最简单的几种设计模式之一,但是感觉要理解每一步的细节也并没有那么简单。
- 目的:保证一个类仅有一个实例,并提供一个访问它的全局访问点。减少系统不断创建、销毁该对象的性能开销。
- 做法:私有化构造方法,让该类自己提供实例,外部访问全局访问点,有实例则返回,无则创建。同时需要确保线程安全。
单例模式的实现有多种方式:懒汉式、饿汉式、双检锁式、静态内部类式(登记式)、枚举式,下面来一一说明这些实现方式的优缺点及实现的代码:
- 懒汉式
public class Singleton1 { private static Singleton1 instance; //私有化构造函数 private Singleton1() {} public static synchronized Singleton1 getIntance() { if(instance==null) { instance = new Singleton1(); } return instance; } }
所谓懒汉式,主要特点就是对对象进行懒加载,也叫延迟加载,即在调用该类的getInstance方法时再进行创建对象。优点是在需要该实例的时候再进行创建,减少内存开销,因为作为单例对象,在创建的过程中往往需要消耗比其他对象更多的系统资源。缺点是调用的效率会低,因为创建对象需要一定的时间,并且为了保证线程安全加了synchronized关键字进行加锁,也降低了时间效率。
- 饿汉式
/** * 单例模式之饿汉式 :线程安全(jvm保证在类加载过程只产生一个对象), * 调用效率高,但不是延迟加载 * @author Administrator * */ public class Singleton2 { private static Singleton2 instance = new Singleton2(); //私有化构造函数 private Singleton2() { } public static Singleton2 getInstance() { return instance; } }
饿汉式在类声明对象属性的过程中就进行了创建对象,根据classLoader机制,保证了线程安全只会创建一个实例。可以看到饿汉式的实现过程相对简单且线程安全,但是并没有延迟加载。
- 双检锁(双重检验锁)
/** * 单例模式之双检锁:线程安全、延迟加载,但调用效率低 * @author Administrator * */ public class Singleton3 { private static volatile Singleton3 instance; private Singleton3() {} public static Singleton3 getInstance() { if(instance==null) {//可能同时有多个线程进入到该语句,在此处等待进入 synchronized(Singleton3.class) { if(instance==null) {//二次校验,可能上一个获取锁的线程已经创建好了实例,故不需要再次创建 instance = new Singleton3(); } } } return instance; } }
首先我们可以看到,双检锁是属于懒汉式的一种升级版,因为将synchronized关键字的范围缩小了,不必每次调用getInstance方法都需要同步,提高了调用时的效率。实现细节上可看代码注释。
- 静态内部类式
/** * 单例模式之静态内部类实现:线程安全,延迟加载,调用效率高 * @author Administrator * */ public class Singleton4 { private static class SingletonHolder{ private static final Singleton4 instance = new Singleton4(); } private Singleton4() {} public static final Singleton4 getInstance() { return SingletonHolder.instance; } }
静态内部类应该是前面几种方式中的最佳实践,它既通过classLoader机制保证了实例化对象时的线程安全,跟饿汉式相比,它还具有延迟加载的功能,我们知道饿汉式在类被装载时就会就会初始化静态的变量和方法,而静态内部类的方式只有显示的调用getInstance方法后才会实例化单例对象,所以做到了懒加载。我们需要注意的是,往往实例化我们要的单例对象是很消耗系统资源的,所以我们想在我们想要实例化的时候在进行实例化,而不是类被装载、类调用其他静态方法时而实例化单例对象,所以使用静态内部类的方式更加合理。
- 枚举式
public enum Singleton5 { //该实例代表单例对象 INSTANCE; //单例对象可以有自己的操作 public void SingletonOperation() { //自己的操作 } }
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
从上面的这段话中我们知道,单例模式的常用实现方式存在着安全漏洞,即通过反射机制和反序列化可以实例化多个不同的对象。那我们该如何去防止和处理这两个漏洞呢?我们的做法是首先实现序列化接口,然后再通过readResolve方法返回我们的实例,这样可以解决反序列化问题。通过再私有的构造方法中判断对象是否存在,已存在则抛异常的方式解决反射的问题:以下是静态内部类实现的方法解决相应问题
public class Singleton4 implements Serializable{ private static class SingletonHolder{ private static final Singleton4 instance = new Singleton4(); } private Singleton4() { if(SingletonHolder.instance!=null) { throw new RuntimeException("can only create one object"); } } public static final Singleton4 getInstance() { return SingletonHolder.instance; } public Object readResolve() throws Exception{ return SingletonHolder.instance; } }
具体的细节可以参考该博客:https://blog.csdn.net/hardwin/article/details/51477359。
以上就是我总结的设计模式之单例模式,如果有写的不对的地方,欢迎评论指出。