思想:

  • 构造器私有
  • 实例化的变量引用私有化
  • 获取实例的方法共有

实现:

饿汉式

/**
 * @author SHshuo
 * @data 2021/8/23--9:51
 * 构造器私有,声明为static静态,一上来就加载全部的资源
 */
public class hungryPattern {

//    构造器私有
    private hungryPattern(){

    }

//    类加载的时候初始化对象
    public final static hungryPattern hungry = new hungryPattern();


//    提供对外接口
    public static hungryPattern getInstance(){
        return hungry;
    }
}


DCL(双重检测模式double checked locking)懒汉式:

为什么双重检查 ?

  • 第一个 if:如果不用第一个if判断,在多线程的情况下,所有的线程都会进行抢锁,也就是串行执行;
  • 第二个 if:为了防止多个线程同时满足为 null 的条件,依次进入重复创建对象。

为什么使用 volatile ?



代码实现:

/**
 * @author SHshuo
 * @data 2021/8/23--9:56
 * 构造器私有,需要用到的资源加载
 * lazyPattern lazy = new lazyPattern();创建对象分为三步:
 * 1.分配内存空间
 * 2.执行构造函数、创建对象
 * 3.将对象指向内存空间
 */
public class lazyPattern {

//    构造器私有
    private lazyPattern(){
        System.out.println(Thread.currentThread().getName());
    }

//    先初始化对象
    private volatile static lazyPattern lazy;


//    对外接口、用到了再创建、并加锁
    public static lazyPattern getInstance(){
//        双重检测锁模式。DCL
        if (lazy == null){
//            加锁、保证高并发
            synchronized (lazyPattern.class){
//                为什么还要加if,因为第一个if不保证原子性
                if(lazy == null){
//                    防止创建对象的时候指令重排,用volatile修饰
                    lazy = new lazyPattern();
                }
            }
        }
        return lazy;
    }

//          测试
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                getInstance();
            }).start();
        }
    }
}


单例模式不安全,可以被反射破坏。通过反射机制调用私有构造器:

//        通过反射机制调用私有构造器
        Constructor<reflexLazyPattern> declaredConstructor = reflexLazyPattern.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
//        创建对象(调用构造函数)
        reflexLazyPattern reflexLazyPattern = declaredConstructor.newInstance();
        System.out.println(reflexLazyPattern);

反射源码分析:jvm内部不允许反射操作枚举。枚举类是天然地可以抵御序列化对单例的破坏和反射攻击的。

源码:

 @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }


枚举实现单例模式,防止反射破坏,最优解。

/**
 * @author SHshuo
 * @data 2021/8/23--14:23
 * enum是天生单例且线程安全的,这种线程安全是jvm层面的,因为jvm保证类加载是线程安全的。
 * 其次,枚举序列化的时候用的是类名和class来找到唯一枚举对象,所以序列化也能保证单例。
 * 最后反射enum的时候也有保证,不论是你反射用了无参的,还是enum(String.class,int.class),
 * 底层都会被阻拦,因为如果是enum类型就会抛出异常,这是jdk层面的保护。
 * <p>
 * <p>
 * 单元素的枚举类型已经成为实现Singleton的最佳方法
 *                                              -- 出自 《effective java》
 */
public enum EnumLazyPattern {

    //    创建一个枚举对象、该对象天生单例
    INSTANCE;

    //    重写构造函数、构造函数默认是private
    EnumLazyPattern() {
        System.out.println(Thread.currentThread().getName());
    }

    //    提供对外接口
    public EnumLazyPattern getInstance() {
        return INSTANCE;
    }

//         测试
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                EnumLazyPattern.INSTANCE.getInstance();
            }).start();
        }
    }
}


补充:枚举实现单例模式:

1.        enum是天生单例且线程安全的,这种线程安全是jvm层面的,因为jvm保证类加载是线程安全的。

2.        枚举序列化的时候用的是类名和class来找到唯一枚举对象,所以序列化也能保证单例。

3.        反射enum的时候也有保证,不论是你反射用了无参的,还是enum(String.class,int.class),底层都会被阻拦,因为如果是enum类型就会抛出异常,这是jdk层面的保护。

4.       单元素的枚举类型已经成为实现Singleton的最佳方法

                                                                                                 -- 出自 《effective java

5.        参考资料:


个人总结:

  1. 枚举实现单例模式是最优解,操作简单,自然解决了反射和序列化带来的问题。
  2. 说白了,就是将实例化变量私有private volatile static  Class className ; 定义成INSTANCE。构造函数私有,对外接口更加简洁,直接返回INSTANCE即可。