目录
一、简介
单例模式是oop(面向对象编程)语言的一种概念,顾名思义,就是一个类只能有一个实例对象。
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
二、单例模式实现方式
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建。
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建。
1、饿汉式单例(推荐)
饿汉式-方式1(静态变量方式)
这种方法通过将对象的实例设置为静态的方式,保证了该对象的实例,永远只有一份,且该对象的创建在类加载的时候就会立即创建在jvm内存中的方法区,在程序运行期间永久存在,所以当我们的对象太大的时候就会造成一种资源的浪费。(会产生大量类加载之后但是没有使用)
java
/**
* 饿汉式
* 静态变量创建类的对象
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance = new Singleton();
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
通过static关键字,在类加载时创建对象。
优化实现:
上述传统方式中,由于类加载时就实例化对象,因此当我们调用这个类的其它静态方法时,也会触发类加载,从而实例化单例独享,会导致空间的暂时浪费。
由于静态内部类中的对象不会默认加载,直到调用了获取该内部类属性的方法。因此可用静态内部类封装静态实例变量。
饿汉式-方式2(静态代码块方式)
对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样。
java
/**
* 饿汉式-方法2
* 在静态代码块中创建该类对象
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
static {
instance = new Singleton();
}
//对外提供静态方法获取该对象
public static Singleton getInstance() {
return instance;
}
}
java
class Singleton{
// 私有构造函数
private Singleton() {}
// 静态内部类
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
2、懒汉式单例
懒汉式-方法1(线程不安全)
java
/**
* 懒汉式
* 线程不安全
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式-方法2(线程安全)
由于可能有多个线程同时要使用对象,因此需要考虑线程安全问题,防止并发访问时生成多个实例。
通常需要加锁来解决并发冲突,是用时间换空间的方案。
java
/**
* 懒汉式
* 线程安全
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//在成员位置创建该类的对象
private static Singleton instance;
//对外提供静态方法获取该对象,并且加锁
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式-方法3(双重锁结构)
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,但是呢,JVM在实例化对象的时候会进行优化和指令重排序操作,在多线程的情况下,就可能会出现空指针问题
对于上述对象的创建主要分为三个部分:
- 分配对象的内存空间。
- 初始化对象。
- 设置instance指针指向对象所在的内存空间。
为了提高性能在JVM在实例化对象的时候会进行优化和指令重排序操作,也就是说有可能将我们上述的第七行和第九行代码进行顺序的交换。
java
/**
* 双重检查方式
*/
public class Singleton {
//私有构造方法
private Singleton() {}
//使用volatile修饰,禁止重排序
private static volatile Singleton instance;
//对外提供静态方法获取该对象
public static Singleton getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
if(instance == null) {
synchronized (Singleton.class) {
//抢到锁之后再次判断是否为空
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
三、单例破坏的方式
1、反射攻击
java
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
// 私有构造
}
public static Singleton getInstance() {
return INSTANCE;
}
}
// 反射攻击代码:
public class ReflectionAttack {
public static void main(String[] args) throws Exception {
Singleton instance1 = Singleton.getInstance();
// 使用反射创建第二个实例
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true); // 突破私有访问限制
Singleton instance2 = constructor.newInstance();
System.out.println(instance1 == instance2); // false!破坏了单例
System.out.println("instance1: " + instance1);
System.out.println("instance2: " + instance2);
}
}
通过类信息进行反射创建。
2、序列化攻击
java
import java.io.*;
public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
// 序列化攻击:
public class SerializationAttack {
public static void main(String[] args) throws Exception {
Singleton instance1 = Singleton.getInstance();
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(instance1);
// 反序列化(会创建新实例!)
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Singleton instance2 = (Singleton) ois.readObject();
System.out.println(instance1 == instance2); // false!
}
}
3、克隆攻击
java
public class CloneableSingleton implements Cloneable {
private static final CloneableSingleton INSTANCE = new CloneableSingleton();
private CloneableSingleton() {}
public static CloneableSingleton getInstance() {
return INSTANCE;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 默认实现会创建新实例
}
}
// 攻击:
CloneableSingleton instance1 = CloneableSingleton.getInstance();
CloneableSingleton instance2 = (CloneableSingleton) instance1.clone();
System.out.println(instance1 == instance2); // false!
4、防御方式
java
// 方法1:使用统一的类加载器(应用类加载器)
// 方法2:在getInstance()中检查类加载器
public class ClassLoaderSafeSingleton {
private static final ClassLoaderSafeSingleton INSTANCE = new ClassLoaderSafeSingleton();
private ClassLoaderSafeSingleton() {
// 检查类加载器
ClassLoader cl = this.getClass().getClassLoader();
if (cl != ClassLoader.getSystemClassLoader()) {
throw new RuntimeException("只能由系统类加载器加载");
}
}
public static ClassLoaderSafeSingleton getInstance() {
return INSTANCE;
}
}