GOF23之单例模式

设计模式GOF23:

创建型模式:

  单例模式、(简单工厂模式)、工厂方法模式、抽象工厂模式、建造者模式、原型模式

结构型模式:

  适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

行为型模式:

  命令模式、模板方法模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

 

单例模式核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

单例模式应用:如Windows系统中的任务管理器和回收站,永远只存在一个。JavaWeb中的Servlet也是单例。

单例模式的优点:

①只生成一个实例,减少了系统的性能开销。当一个对象的产生需要较多的性能资源时,比如读取配置文件或者依赖于其他对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存来解决。

②单例模式可以在系统设置永久的访问点,优化共享资源的访问。

常见的五种单例模式实现方法:

  一、主要:

    * 饿汉式:类加载的时候就生成单例对象。线程安全,调用效率高,不能延迟加载。会被反射/序列化破坏单例。

    * 懒汉式:使用该对象时才生成单例对象。线程安全,调用效率低,可以延迟加载。会被反射/序列化破坏单例。

  二、其他:

    * 双重检测锁机制:懒汉式的一个变种。只在创建对象过程中加锁,可以提高效率。同时使用volatile和synchronized对创建过程加锁,保证了线程安全性。会被反射/序列化破坏单例。

    * 静态内部类机制:懒汉式的一个变种。由于JVM在加载外部类时,是不会去加载静态内部类的,只有在静态内部类的属性/方法被调用的时候才会被加载并初始化其静态属性。由于静态属性被static修饰,就会保证只实例化一次,并且严格保证了实例化顺序。会被反射/序列化破坏单例。

    * 枚举:枚举是Java底层提供的特殊单例类,枚举是线程安全的,且只会加载一次,属于饿汉式的一个变种。且枚举类型是唯一一个不会被反射/序列化破坏的单例模式

代码实现:

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;
}
posted @ 2019-08-04 09:16  砖厂繁忙在下告辞  阅读(156)  评论(0编辑  收藏  举报