GOF23之单例模式
设计模式GOF23:
创建型模式:
单例模式、(简单工厂模式)、工厂方法模式、抽象工厂模式、建造者模式、原型模式
结构型模式:
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
行为型模式:
命令模式、模板方法模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
单例模式核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
单例模式应用:如Windows系统中的任务管理器和回收站,永远只存在一个。JavaWeb中的Servlet也是单例。
单例模式的优点:
①只生成一个实例,减少了系统的性能开销。当一个对象的产生需要较多的性能资源时,比如读取配置文件或者依赖于其他对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存来解决。
②单例模式可以在系统设置永久的访问点,优化共享资源的访问。
常见的五种单例模式实现方法:
一、主要:
* 饿汉式:类加载的时候就生成单例对象。线程安全,调用效率高,不能延迟加载。会被反射/序列化破坏单例。
* 懒汉式:使用该对象时才生成单例对象。线程安全,调用效率低,可以延迟加载。会被反射/序列化破坏单例。
二、其他:
* 双重检测锁机制:
* 静态内部类机制:
* 枚举:
代码实现:
1、饿汉式
import java.io.ObjectStreamException; import java.io.Serializable; /** * Title: SingletonDemo01类 * Description: 单例模式-饿汉式 * * @author 杨万浩 * @version 1.0 */ public class SingletonDemo01 { /* * 类初始化时,立即加载这个对象 * 由于对象是初始化时就创建好的,所以不管几个线程访问的都是这个对象 */ private static SingletonDemo01 instance = new SingletonDemo01(); //私有的构造方法 private SingletonDemo01(){ } /* * 线程安全的,即使没有synchronized修饰也是线程安全的 * 由于不需要synchronized修饰,方法没有同步锁,所以效率高 */ public static SingletonDemo01 getInstance() { return instance; }
}
2、懒汉式
/** * Title: SingletonDemo02类 * Description: 单例模式-懒汉式 * * @author 杨万浩 * @version 1.0 */ public class SingletonDemo02 { // 类初始化时,不会初始化这个对象,真正使用时再创建 private static SingletonDemo02 instance; // 私有的构造方法 private SingletonDemo02 (){} /* * 可以懒加载,真正需要这个对象时再创建这个对象 * 为了线程安全,需要加synchronized同步锁,也会因此而效率低 */ public static synchronized SingletonDemo02 getInstance() { if( instance == null ) { instance = new SingletonDemo02(); } return instance; } }
3、双重检测锁机制
/** * Title: SingletonDemo03类 * Description: 单例模式-双重检测锁式 * * @author 杨万浩 * @version 1.0 */ public class SingletonDemo03 { private volatile static SingletonDemo03 instance; private SingletonDemo03(){} private static SingletonDemo03 getInstance() { if( instance == null ) { SingletonDemo03 sc; synchronized (SingletonDemo03.class) { sc = instance; if( sc == null ) { synchronized (SingletonDemo03.class) { sc = new SingletonDemo03(); } instance = sc; } } } return instance; } }
4、静态内部类实现
/** * Title: SingletonDemo04类 * Description: 单例模式-静态内部类实现 * * @author 杨万浩 * @version 1.0 */ public class SingletonDemo04 { private static class SingletonClassInstance{ private static final SingletonDemo04 instance = new SingletonDemo04(); } private SingletonDemo04() {} public static SingletonDemo04 getInstance() { return SingletonClassInstance.instance; } }
5、枚举
/** * Title: SingletonDemo05类 * Description: 单例模式-枚举方式 * * @author 杨万浩 * @version 1.0 */ public enum SingletonDemo05 { STUDENT1(21,"张三"),STUDENT2(20,"李四"),STUDENT3(22,"王五"); int age; String name; private SingletonDemo05(int age, String name) { this.age = age; this.name = name; } }
但是,上边五种方法,除了最后一种(枚举实现)之外,其他四种都会存在两个漏洞。
1、反射漏洞
示例代码:
//通过正规途径获取到的对象s1和s2 SingletonDemo01 s1 = SingletonDemo01.getInstance(); SingletonDemo01 s2 = SingletonDemo01.getInstance(); //通过反射获取到的对象s3 Class clazz = Class.forName("cn.yangwanhao.singleton.SingletonDemo01"); Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); SingletonDemo01 s3 = (SingletonDemo01) constructor.newInstance(); //输出这三个对象的地址 System.err.println(s1 + " : s1"); System.err.println(s2 + " : s2"); System.err.println(s3 + " : s3");
输出结果:
cn.yangwanhao.singleton.SingletonDemo01@50134894 : s1 cn.yangwanhao.singleton.SingletonDemo01@50134894 : s2 cn.yangwanhao.singleton.SingletonDemo01@5fdef03a : s3
由输出结果可以看到,s1和s2是同一个对象,而s3就是另外一个对象了。
修补方法:在私有的构造方法里加上一个if判断,再次通过上述反射代码获取对象时会报异常
//私有的构造方法 private SingletonDemo01(){ if( instance != null ) { throw new RuntimeException(); } }
2、反序列化漏洞
示例代码:
//通过正规途径获取到的对象s1和s2 SingletonDemo01 s1 = SingletonDemo01.getInstance(); SingletonDemo01 s2 = SingletonDemo01.getInstance(); //通过将s1序列化到磁盘再反序列化得到的s4 FileOutputStream ops = new FileOutputStream("d:/a.txt"); ObjectOutputStream oops = new ObjectOutputStream(ops); oops.writeObject(s1); ops.close(); oops.close(); FileInputStream ois = new FileInputStream("d:/a.txt"); ObjectInputStream oips = new ObjectInputStream(ois); SingletonDemo01 s4 = (SingletonDemo01) oips.readObject(); System.err.println(s1 + " : s1"); System.err.println(s2 + " : s2"); System.err.println(s4 + " : s4");
输出结果:
cn.yangwanhao.singleton.SingletonDemo01@50134894 : s1 cn.yangwanhao.singleton.SingletonDemo01@50134894 : s2 cn.yangwanhao.singleton.SingletonDemo01@5fdef03a : s4
结论同上
修补方法:在单例类中加入以下方法代码
private Object readResolve() throws ObjectStreamException { return instance; }