单例模式

1.定义

单例模式是确保全局只有一个实例,并且提供一个全局的访问点,属于创建型模式,典型应用就是枚举

2.饿汉式单例模式

饿汉式单例模式在类加载的时候就已经创建,属于线程安全模式,因为那个时候还没有线程,在线程出现以前就已经实例化了,代码:

/**
 * 优点:执行效率高,性能高,没有任何的锁
 * 缺点:某些情况下,可能会造成内存浪费
 * create by yufeng on 2021/7/3 21:00
 */
public class HungrySingleton {

    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return  hungrySingleton;
    }
}

/**
 * 静态代码块的形式
 * create by yufeng on 2021/7/3 21:00
 */
public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungrySingleton;


    static {
        hungrySingleton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton(){}

    public static HungryStaticSingleton getInstance(){
        return  hungrySingleton;
    }
}
3.懒汉式单例模式

为了饿汉模式带来的内存浪费问题,就弄了个懒汉模式,因为只有在被用到的时候才会被实例化,所以就避免了内存浪费的问题


/**
 * 优点:节省了内存,线程安全
 * 缺点:性能低
 * create by yufeng on 2021/7/3 21:12
 */
public class LazySimpleSingletion {
    private static LazySimpleSingletion instance;
    private LazySimpleSingletion(){}

    public synchronized static LazySimpleSingletion getInstance(){
        if(instance == null){
            instance = new LazySimpleSingletion();
        }
        return instance;
    }
}

为了解决上面性能低的问题,用双重检查锁的方法:
  /**
 * 优点:性能高了,线程安全了
 * 缺点:可读性难度加大,不够优雅
 * create by yufeng on 2021/7/3 21:12
 */
public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton instance;
    private LazyDoubleCheckSingleton(){}

    public static LazyDoubleCheckSingleton getInstance(){
        //检查是否要阻塞
        if (instance == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                //检查是否要重新创建实例
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                    //指令重排序的问题
                }
            }
        }
        return instance;
    }
}

但是第二种写法还是有锁的问题,存在性能的问题,这个时候就用内部类的形式来解决

/*
  兼顾了饿汉模式的浪费内存,和懒汉模式锁性能的问题
   create by yufeng on 2021/7/3 21:12
 */
public class LazyInnerClassSingleton {

  // 使用的时候默认先加载内部类
    private LazyInnerClassSingleton(){
    }

    private static final LazyInnerClassSingleton getInstance(){
        return LazyHolder.LAZY;
    }

  // 默认不加载
    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }

}
    

但是上面的方法还是可以通过反射进行破坏单例模式,所以在上面的基础实例化前加个校验

/*
  ClassPath : LazyStaticInnerClassSingleton.class
              LazyStaticInnerClassSingleton$LazyHolder.class
   优点:写法优雅,利用了Java本身语法特点,性能高,避免了内存浪费,不能被反射破坏
   缺点:不优雅
   create by yufeng on 2021/7/3 21:12
 */
public class LazyStaticInnerClassSingleton {

    private LazyStaticInnerClassSingleton(){
        if(LazyHolder.INSTANCE != null){
            throw new RuntimeException("不允许非法访问");
        }
    }

    private static LazyStaticInnerClassSingleton getInstance(){
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder{
        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
    }

}
4.序列化

序列化和反序列化后,单例模式会被破坏,因为反序列化重新开辟内存重新创建对象,违背了单例的初衷

/**
 * create by yufeng on 2021/7/3 21:30
 */

public class SeriableSingleton implements Serializable {


    //序列化
    //把内存中对象的状态转换为字节码的形式
    //把字节码通过IO输出流,写到磁盘上
    //永久保存下来,持久化

    //反序列化
    //将持久化的字节码内容,通过IO输入流读到内存中来
    //转化成一个Java对象
    public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(){}

    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }

    private Object readResolve(){ return INSTANCE;}

}

通过jdk源码分析我们可以看到readResolve()方法虽然解决了序列化破坏单例的问题,但是实际上是实例化了2次,只不过新创建的对象没有被返回而已,如果该动作频率加快内存分配开销也会加大。注册式模式会解决该问题

5.注册式单例模式

注册式单例模式,也叫登记式单例模式,每一个实例都会登记到某一个地方,使用唯一的标识获取实例,注册式单例模式有2中:枚举、容器

5.1枚举式单例模式

类似饿汉模式,类加载的是就会实例化,也不会被序列化破坏,因为枚举式用过类名和类对象找到唯一对象,所以不能被加载多次,也不会通过反射破坏,因为没无参构造,只有一个protected类型的构造方法,在newInstance() 时有个强制判断,如果是枚举那就直接抛出异常,跟静态内部类类似的处理。

/**
 * create by yufeng on 2021/7/3 22:30
 */
public enum EnumSingleton {
    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance(){return INSTANCE;}
}
5.2容器式单例模式

枚举式单例跟饿汉模式差不多,都会加载的时候就进行了实例化,不适合大量创建单例对象的场景,所以就有了容器式单例模式

/**
 * create by yufeng on 2021/7/3 22:30
 */
public class ContainerSingleton {

    private ContainerSingleton(){}

    private static Map ioc = new ConcurrentHashMap();

    public static Object getInstance(String className){
        Object instance = null;
        if(!ioc.containsKey(className)){
            try {
                instance = Class.forName(className).newInstance();
                ioc.put(className, instance);
            }catch (Exception e){
                e.printStackTrace();
            }
            return instance;
        }else{
            return ioc.get(className);
        }
    }

}

容器式单例模式适用于需要大量创建单例模式对象的场景,但是不是线程安全的

5.源码地址

单例模式源码地址

你可能感兴趣的:(单例模式)