单例模式及jdk中的应用

单例

饿汉模式一

package com.design_mode.singleton;

import java.io.Serializable;

/**
 * @author liu yue
 * @description: 单例模式1:
 *  饿汉模式
 *  1.构造私有
 *  2.类加载时初始化静态私有单例对象
 *  3.提供一个获取实例的静态方法
 *
 * TODO
 *  破坏单例模式
 *  1.反射, 使用构造方法,
 *      预防方法: 构造方法中抛异常
 *  2.实现了序列化接口的类可以被反序列化, 不走构造方法,通过流创建实例
 *      预防方法: 重写readResolve方法,反序列化时,发现重写了方法,以当前方法的返回值进行返回, 如果发现是枚举类时, 反序列化时,直接返回枚举,不会创建对象
 *  3.unsafe进行破坏, 不走构造方法
 *      预防方法: 目前没有搜到有关资料
 *
 * @date 2022/7/7 19:27
 */
public class Singleton1 implements Serializable {

    private Singleton1(){
        //TODO 防止反射创建,破坏单例模式
        if (SINGLETON_1 != null){
            throw new RuntimeException("单例对象不能重复创建");
        }

        System.out.println("private Singleton1 init...");
    }

    private static final Singleton1 SINGLETON_1 = new Singleton1();

    public static Singleton1 getSingleton1Instance(){
        return SINGLETON_1;
    }

    public void go(){
        System.out.println("Singleton1 go...");
    }

    //TODO 防止反序列化生成对象,破坏单例模式
    public Object readResolve(){
        return SINGLETON_1;
    }

}

饿汉模式二

package com.design_mode.singleton;

/**
 * @author liu yue
 * @description: 枚举类实现饿汉单例模式: 类加载就被创建
 *  枚举类在编译时期,会继承Enum<?>父类,被添加static代码块,创建实例对象
 *  这里的ordinal就是S1与S2的先后顺序
 *	字节码部分内容:
 *  public static final Singleton2_enum S1;
 *  public static final Singleton2_enum S2;
 *  private Singleton2_enum(String name, int ordinal){ super(name,ordinal}
 *  static{
 *      Singleton2_enum S1 = new Singleton2_enum("S1",0);
 *      Singleton2_enum S2 = new Singleton2_enum("S2",1);
 *  }
 *
 *  TODO 单例模式破坏情况
 *      1.反序列化: 不会破坏, 因为ObjectInputStream中反序列化时,对枚举类进行了优化处理, 遇见了枚举类,直接返回其实例对象
 *      2.反射: 不能破坏, 因为枚举只有私有有参构造, 且构造不允许反射创建
 * @date 2022/7/7 20:10
 */
public enum Singleton2_enum {
    S1;
    //,S2;


    //看看无参执行了吗
    Singleton2_enum(){
        System.out.println("Singleton2_enum init...");
    }

    //可以不提供获取方法, 枚举就是公共的可获取的
    public static Singleton2_enum getInstance1(){
        return S1;
    }
//    可以不提供获取方法, 枚举就是公共的可获取的
/*    public static Singleton2_enum getInstance2(){
        return S2;
    }*/

    //重新toString, 打印当前对象
    //不重写时,打印的就是 S1 或 S2 对象的名字
    @Override
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    public static void go(){
        System.out.println("enum go....");
    }
}

懒汉模式一: dcl

package com.design_mode.singleton;

import java.io.Serializable;

/**
 * @author liu yue
 * @description: 单例模式_懒汉模式  DCL: double check lock 双检锁
 * @date 2022/7/7 20:56
 */
public class Singleton3 implements Serializable {
    private static final Object object = new Object();
    //TODO volatile在这里主要用于禁止指令重排序
    private static volatile Singleton3 SINGLETON_3;

    private Singleton3() {
        //TODO 预防反射创建对象
        if (SINGLETON_3 != null){
            throw new RuntimeException("单例模式,不允许反射创建对象");
        }
        System.out.println("singleton init...");
    }

    public static Singleton3 getSingleton3() {
        //TODO double check 外层判断防止每次进入方法都抢锁
        if (SINGLETON_3 == null) {
            synchronized (object) {//加锁
                //TODO 内存判断,防止多个线程进行创建对象,因为可能多个线程进入第一个if,被卡在锁外
                if (SINGLETON_3 == null) {
                    //TODO volatile 禁止指令重排序
                    // 如果此时正在创建对象, 发生了指令重排,导致先给SINGLETON_3赋值地址,
                    // 但其内部并未完成初始化,此时另外的线程可能拿着这个未初始化的对象进行操作,引发异常
                    SINGLETON_3 = new Singleton3();
                }
            }
        }
        return SINGLETON_3;
    }


    //TODO 预防反序列化破坏单例
    public Object readResolve(){
        return SINGLETON_3;
    }

}

单例模式二: 内部类

package com.design_mode.singleton;

/**
 * @author liu yue
 * @description: 单例模式 内部类 懒汉模式
 * @date 2022/7/8 0:18
 */
public class Singleton4 {

    private Singleton4(){
        System.out.println("single4 init...");
    }


    //内部类的静态属性, 如果没有使用到内部类, 内部内属性不会初始化
    private static class Holder{
        static Singleton4 singleton4 = new Singleton4();
    }


    //获取实例时, 由于Holder中的实例是静态的, jvm保证了线程安全
    public static Singleton4 getInstance4(){
        return Holder.singleton4;
    }


    public void go(){
        System.out.println("singleton4 go...");
    }

}

破坏单例

package com.design_mode.singleton;

import org.springframework.objenesis.instantiator.util.UnsafeUtils;

import java.io.*;
import java.lang.reflect.Constructor;

/**
 * @author liu yue
 * @description: 破坏单例模式
 * @date 2022/7/7 19:42
 */
public class DestroySingleton {

    //TODO 反射. 可以被预防
    public static Object reflect(Class<?> clazz) throws Exception {
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        return constructor.newInstance();
    }
    //TODO 反射创建枚举类对象不行,
    public static Object reflect2(Class<?> clazz,int i) throws Exception {
        //即使走有参构造也不行
        Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        return constructor.newInstance("other", i);
    }


    //TODO 序列化 不走构造方法. 可以被预防
    public static Object serializable(Object instance) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(instance);

        //TODO 解析枚举类对象时, ObjectInputStream会对枚举进行处理, 直接返回枚举类的对象; 而不会使用反序列化数组生成的结果
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        return ois.readObject();
    }

    //TODO unsafe破坏. 目前没搜索到预防方法
    public static Object unsafeDestroy(Class<?> clazz) throws Exception {
        //使用Spring的工具类,获取Unsafe对象实例, 并根据class对象创建实例
        return UnsafeUtils.getUnsafe().allocateInstance(clazz);
    }

}

测试单例

饿汉模式1测试

package com.design_mode.singleton;

/**
 * @author liu yue
 * @description: 单例模式1 普通单例模式 饿汉模式1测试
 * @date 2022/7/7 21:06
 */
public class Singleton1_Test {
    public static void main(String[] args) throws Exception {
        //TODO 测试饿汉单例1 反射 序列化 unsafe都能破坏单例模式1, 反射和序列化可以预防, unsafe不能预防
        Singleton1 singleton1 = Singleton1.getSingleton1Instance();
        System.out.println(singleton1);//获取单例1
        System.out.println(singleton1);//检验单例

        try {
            System.out.println(DestroySingleton.reflect(singleton1.getClass()));//测试反射
        }catch (Exception e){
            System.out.println("反射失败");
            e.printStackTrace();
        }

        System.out.println("反序列化破坏饿汉模式1:  " +
                DestroySingleton.serializable(Singleton1.getSingleton1Instance()));//测试反序列化

        System.out.println("unsafe破话饿汉模式1:  " +
                DestroySingleton.unsafeDestroy(singleton1.getClass()));//测试unsafe破坏. 目前没搜到预防手段

        System.out.println();
    }
}

饿汉模式2测试

package com.design_mode.singleton;

/**
 * @author liu yue
 * @description: 单例模式2 枚举类 饿汉单例模式2测试
 * @date 2022/7/7 21:08
 */
public class Singleton2_enum_Test {
    public static void main(String[] args) throws Exception{
        //TODO 测试饿汉单例2: 枚举  反射和序列化都不能破坏枚举的单例模式, unsafe依然能够破坏枚举单例模式
        Singleton2_enum s1 = Singleton2_enum.S1;
        System.out.println(Singleton2_enum.S1);//获取实例
        System.out.println(Singleton2_enum.S1);//测试两次获取的是否为同一个

        try {
            //TODO 枚举类没有无参构造, 不能反射创建,
            System.out.println(DestroySingleton.reflect(s1.getClass()));//反射不能破坏枚举的单例模式
        }catch (Exception e){
            System.out.println("反射1失败");
            e.printStackTrace();
        }

        try {
            //TODO 使用有参构造时, 也会被抛出异常: 非法参数异常: 不能使用反射创建枚举
            System.out.println(DestroySingleton.reflect2(s1.getClass(), 1));//反射不能破坏枚举的单例模式
        }catch (Exception e){
            System.out.println("反射2失败");
            e.printStackTrace();
        }

        //TODO 反序列化,不能破坏枚举的单例模式
        System.out.println("反序列化枚举:  " + DestroySingleton.serializable(s1));
        //TODO unsafe依然能够破坏枚举单例
        System.out.println("unsafe枚举:  " + DestroySingleton.unsafeDestroy(s1.getClass()));

        System.out.println();
    }
}

懒汉模式1 dcl测试

package com.design_mode.singleton;

/**
 * @author liu yue
 * @description: 单例模式3 普通单例 懒汉模式1测试 DCL: double check lock 双检锁
 * @date 2022/7/7 21:11
 */
public class Singleton3_Test {
    public static void main(String[] args) throws Exception{
        //TODO 测试饿汉单例1 反射 序列化 unsafe都能破坏单例模式1, 反射和序列化可以预防, unsafe不能预防
        Singleton3 singleton3 = Singleton3.getSingleton3();
        System.out.println(singleton3);//获取单例1
        System.out.println(singleton3);//检验单例

        try {
            System.out.println("反射创建实例:  " + DestroySingleton.reflect(singleton3.getClass()));//测试反射
        }catch (Exception e){
            System.out.println("反射失败");
            e.printStackTrace();
        }

        System.out.println("反序列化破坏饿汉模式1:  " +
                DestroySingleton.serializable(Singleton3.getSingleton3()));//测试反序列化

        System.out.println("unsafe破坏饿汉模式1:  " +
                DestroySingleton.unsafeDestroy(singleton3.getClass()));//测试unsafe破坏. 目前没搜到预防手段

        System.out.println();
    }
}

jdk源码中的单例

饿汉

  • System.exit();
  • System.gc();
  • Collections中的很多空集合 EMPTY开头的集合
  • 枚举类饿汉: Collections中的内部类ReverseComparator中的reversed方法获取一个正序比较器,
    其获取的return Comparator.naturalOrder() —>
    (Comparator) Comparators.NaturalOrderComparator.INSTANCE —> Comparators的内部枚举类

懒汉

  • 1.双检锁: System中的Console控制台对象
  • 2.Collections中的很多空集合 EMPTY开头的集合
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值