加入收藏 | 设为首页 | 会员中心 | 我要投稿 安卓应用网 (https://www.0791zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 程序设计 > 正文

设计模式 | 单例模式及典型应用

发布时间:2020-05-29 06:31:06 所属栏目:程序设计 来源:互联网
导读:单例是最常见的设计模式之一,实现的方式非常多,同时需要注意的问题也非常多。本文主要内容:介绍单例模式介绍单例模式的N中写法单例模式的安全性序列化攻击反射攻击单例模式总结介绍单例模式的典型应用单例模式单例模式(Singleton Pattern):确保某一个类只

单例是最常见的设计模式之一,实现的方式非常多,同时需要注意的问题也非常多。

本文主要内容:

  • 介绍单例模式
  • 介绍单例模式的N中写法
  • 单例模式的安全性
    • 序列化攻击
    • 反射攻击
  • 单例模式总结
  • 介绍单例模式的典型应用

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

单例模式有三个要点:

  1. 构造方法私有化;
  2. 实例化的变量引用私有化;
  3. 获取实例的方法共有

Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的 getInstance() 工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个 Singleton 类型的静态对象,作为外部共享的唯一实例。

// 线程安全
public class Singleton {
private final static Singleton INSTANCE = new Singleton();

private Singleton(){}

public static Singleton getInstance(){
    return INSTANCE;
}

}

优点:简单,使用时没有延迟;在类装载时就完成实例化,天生的线程安全

缺点:没有懒加载,启动较慢;如果从始至终都没使用过这个实例,则会造成内存的浪费。

// 线程安全
public class Singleton {
private static Singleton instance;

static {
    instance = new Singleton();
}

private Singleton() {}

public static Singleton getInstance() {
    return instance;
}

}

将类实例化的过程放在了静态代码块中,在类装载的时执行静态代码块中的代码,初始化类的实例。优缺点同上。

// 线程不安全
public class Singleton {
private static Singleton singleton;

private Singleton() {}

public static Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

}

优点:懒加载,启动速度快、如果从始至终都没使用过这个实例,则不会初始化该实力,可节约资源

缺点:多线程环境下线程不安全。if (singleton == null) 存在竞态条件,可能会有多个线程同时进入 if 语句,导致产生多个实例

// 线程安全,效率低
public class Singleton {
private static Singleton singleton;

private Singleton() {}

public static synchronized Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

}

优点:解决了上一种实现方式的线程不安全问题

缺点:synchronized 对整个 getInstance() 方法都进行了同步,每次只有一个线程能够进入该方法,并发性能极差

// 线程安全
public class Singleton {
    // 注意:这里有 volatile 关键字修饰
    private static volatile Singleton singleton;
private Singleton() {}

public static Singleton getInstance() {
    if (singleton == null) {
        synchronized (Singleton.class) {
            if (singleton == null) {
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

}

优点:线程安全;延迟加载;效率较高。

由于 JVM 具有指令重排的特性,在多线程环境下可能出现 singleton 已经赋值但还没初始化的情况,导致一个线程获得还没有初始化的实例。volatile 关键字的作用:

  • 保证了不同线程对这个变量进行操作时的可见性
  • 禁止进行指令重排序

// 线程安全
public class Singleton {
private Singleton() {}

private static class SingletonInstance {
    private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
    return SingletonInstance.INSTANCE;
}

}

优点:避免了线程不安全,延迟加载,效率高。

静态内部类的方式利用了类装载机制来保证线程安全,只有在第一次调用getInstance方法时,才会装载SingletonInstance内部类,完成Singleton的实例化,所以也有懒加载的效果。

加入参数 -verbose:class 可以查看类加载顺序

$ javac Singleton.java
$ java -verbose:class Singleton

// 线程安全
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}

优点:通过JDK1.5中添加的枚举来实现单例模式,写法简单,且不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

单例模式的目标是,任何时候该类都只有唯一的一个对象。但是上面我们写的大部分单例模式都存在漏洞,被攻击时会产生多个对象,破坏了单例模式。

通过Java的序列化机制来攻击单例模式

public class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();
    private HungrySingleton() {
    }
    public static HungrySingleton getInstance() {
        return instance;
    }
public static void main(String[] args) throws IOException,ClassNotFoundException {
    HungrySingleton singleton = HungrySingleton.getInstance();
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
    oos.writeObject(singleton); // 序列化

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton_file"));
    HungrySingleton newSingleton = (HungrySingleton) ois.readObject(); // 反序列化

    System.out.println(singleton);
    System.out.println(newSingleton);
    System.out.println(singleton == newSingleton);
}

}

结果

com.singleton.HungrySingleton@ed17bee
com.singleton.HungrySingleton@46f5f779
false

Java 序列化是如何攻击单例模式的呢?我们需要先复习一下Java的序列化机制

java.io.ObjectOutputStream 是Java实现序列化的关键类,它可以将一个对象转换成二进制流,然后可以通过 ObjectInputStream 将二进制流还原成对象。具体的序列化过程不是本文的重点,在此仅列出几个要点。

Java 序列化机制的要点:

  • 需要序列化的类必须实现java.io.Serializable接口,否则会抛出NotSerializableException异常
  • 若没有显示地声明一个serialVersionUID变量,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较(验证一致性),如果检测到反序列化后的类的serialVersionUID和对象二进制流的serialVersionUID不同,则会抛出异常
  • Java的序列化会将一个类包含的引用中所有的成员变量保存下来(深度复制),所以里面的引用类型必须也要实现java.io.Serializable接口
  • 当某个字段被声明为transient后,默认序列化机制就会忽略该字段,反序列化后自动获得0或者null值
  • 静态成员不参与序列化
  • 每个类可以实现readObjectwriteObject方法实现自己的序列化策略,即使是transient修饰的成员变量也可以手动调用ObjectOutputStreamwriteInt等方法将这个成员变量序列化。
  • 任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例
  • 每个类可以实现private Object readResolve()方法,在调用readObject方法之后,如果存在readResolve方法则自动调用该方法,readResolve将对readObject的结果进行处理,而最终readResolve的处理结果将作为readObject的结果返回。readResolve的目的是保护性恢复对象,其最重要的应用就是保护性恢复单例、枚举类型的对象
  • Serializable接口是一个标记接口,可自动实现序列化,而Externalizable继承自Serializable,它强制必须手动实现序列化和反序列化算法,相对来说更加高效

根据上面对Java序列化机制的复习,我们可以自定义一个 readResolve,在其中返回类的单例对象,替换掉 readObject 方法反序列化生成的对象,让我们自己写的单例模式实现保护性恢复对象

public class HungrySingleton implements Serializable {
    private static final HungrySingleton instance = new HungrySingleton();
    private HungrySingleton() {
    }
    public static HungrySingleton getInstance() {
        return instance;
    }
private Object readResolve() {
    return instance;
}

public static void main(String[] args) throws IOException,ClassNotFoundException {
    HungrySingleton singleton = HungrySingleton.getInstance();
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton_file"));
    HungrySingleton newSingleton = (HungrySingleton) ois.readObject();

    System.out.println(singleton);
    System.out.println(newSingleton);
    System.out.println(singleton == newSingleton);
}

}

再次运行

com.singleton.HungrySingleton@24273305
com.singleton.HungrySingleton@24273305
true

注意:自己实现的单例模式都需要避免被序列化破坏

(编辑:安卓应用网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读