单例模式详解 单例模式

基础概念

单例模式是什么有什么用

单例模式保证一个类只有一个实例,并提供一个可以访问该实例的方法

单例模式的几种写法

  1. 饿汉式

    饿汉式是在初始化时就将单例对象创建出来。通常通过属性new创建自身。由JVM保证线程安全,但会造成内存资源的浪费

    //饿汉式
    public class Singleton {
        //私有成员变量
        private static Singleton singleton;
        
        private Singleton(){
            
        }
        public static Singleton getInstance(){
            return singleton;
        }
    }
    
  2. 懒汉式

    懒汉式是指在第一次使用的时候才将对象创建出来,是线程不安全的,但是不会造成内存资源的浪费

    //懒汉式
    public class Singleton {
        
        private static Singleton singleton = null;
    
        private Singleton(){
    
        }
        public static Singleton getInstance(){
            if(singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    

    这种方法的不安全性主要在当还没有对象时,有多个线程同时运行,发现没有实例对象,就自己生成了实例对象,这样就有可能出现多个实例对象

  3. 双重校验锁

    //双重校验锁
    public class Singleton {
        //使用volatile关键字保证指令不会被重排序
        private volatile static Singleton singleton = null;
        private Singleton(){
    
        }
        
        public static Singleton getInstance(){
            //如果没有进行实例化
            if(singleton == null) {
                //将整个类锁住
                synchronized (Singleton.class) {
                    //确保没有进行实例化
                    if(singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    
  4. 静态内部类

    //静态内部类
    public class StaticSingleton {
        //私有化构造器
        private StaticSingleton() {
            
        }
        //使用内部类创建对象,由JVM保证线程安全
        private static class SingletonFactory {
            private static StaticSingleton singleton = new StaticSingleton();
        }
        public static StaticSingleton getInstance() {
            return SingletonFactory.singleton;
        }
    }
    
  5. 枚举

    public enum SingletonEnum {
        INSTANCE;
    }
    

如何破坏单例模式

  1. 反射

    通过反射获取单例对象的构造器,暴力破解后即可创建多个实例

    @Test
    public static void reflectTest() {
    Singleton singleton = Singleton.getInstance();
            Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton singleton2 = constructor.newInstance(null);
        System.out.println(singleton == singleton2);
    }
    
  2. 序列化

    通过深克隆复制对象,可以生成多个实例

    @Test
    public static void serializationTest() {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("objectFile"));
            Singleton singleton3 = Singleton.getInstance();
            os.writeObject(singleton3);
            os.close();
            ObjectInputStream is = new ObjectInputStream(new FileInputStream(new File("objectFile")));
            Singleton singleton4 = (Singleton) is.readObject();
            is.close();
            System.out.println(singleton3 == singleton4);
    }
    

如何防止单例模式被破坏

  1. 防止反射破坏单例模式

    新增一个volatile变量isFirstCreate,在私有构造方法里面对该变量进行双校验。

  2. 防止序列化破坏单例模式

    继承Serializable接口,并重写readObject()方法

import java.io.ObjectStreamException;
import java.io.Serializable;

// 能防止被反射、序列化破坏的双重校验单例模式
public class Singleton implements Serializable {
    private volatile static Singleton singleton = null;
    private volatile static boolean isFirstCreate = true;
    private Singleton(){
        //防止被反射破坏单例
        if (isFirstCreate) {
            synchronized (Singleton.class) {
                if(isFirstCreate) {
                    isFirstCreate = false;
                }
                else {
                    throw new RuntimeException("此单例已经创建过了");
                }
            }
        }
        else {
            throw new RuntimeException("此单例已经存在");
        }
    }
    public static Singleton getInstance(){
        if(singleton == null) {
            synchronized (Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    //防止被序列化生成多个实例
    private Object readResolve() throws ObjectStreamException {
        return singleton;
    }
}