深入理解编写单例模式

单例模式

饿汉式

饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。

优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。

缺点:类加载的时候就初始化,不管用与不用都占着空间,一旦单例对象相对复杂,浪费了很多内存,有创建后不用。

/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:00
 */
public class HungrySingleton {
    private  static final HungrySingleton HungrySingleton = new HungrySingleton();
    private HungrySingleton() {}

    public static HungrySingleton getInstance() {
        return HungrySingleton;
    }
}
/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:00
 */
public class HungryStaticSingleton {
    private  static final HungryStaticSingleton HUNGRY_STATIC_SINGLETON;
    static {
        HUNGRY_STATIC_SINGLETON   = new HungryStaticSingleton();
    }
    private HungryStaticSingleton() {}

    public static HungryStaticSingleton getInstance() {
        return HUNGRY_STATIC_SINGLETON;
    }
}

懒汉式

懒汉式单例的特点是:被外部类调用的时候内部类才会加载

/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:06
 */
public class LazySimpleSingleton {
    private static LazySimpleSingleton lazySimpleSingleton;

    private LazySimpleSingleton(){}
    //这个写法在多线程时候执行,会出现线程安全问题。
    public static LazySimpleSingleton getInstance() {
        if (lazySimpleSingleton == null) {
             lazySimpleSingleton = new LazySimpleSingleton();
        }
        return lazySimpleSingleton;
    }
}
/**
 * @author DK
 * @version 1.0
 * @date 2020-06-08 10:10
 **/
public class Main {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                try {
                    countDownLatch.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(LazySimpleSingleton.getInstance());
            }).start();
            countDownLatch.countDown();
        }
    }
}

在这里插入图片描述

这样就会出现线程安全问题

/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:06
 */
public class LazySimpleSingleton {
    private static LazySimpleSingleton lazySimpleSingleton;

    private LazySimpleSingleton(){}
    //为了线程安全我们往往会加 synchronized ,但同一时间大量调用这个获取单例方式就会拥堵获取对象相率相当低
    public static synchronized LazySimpleSingleton getInstance() {
        if (lazySimpleSingleton == null) {
             lazySimpleSingleton = new LazySimpleSingleton();
        }
        return lazySimpleSingleton;
    }
}
/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:06
 */
public class LazySimpleSingleton {
    //volatile 是禁止指令重排序
    private volatile  static LazySimpleSingleton lazySimpleSingleton;

    private LazySimpleSingleton(){}
	//这里我们会采用double check 机制
    public static LazySimpleSingleton getInstance() {
        if (lazySimpleSingleton == null) {
            synchronized (LazySimpleSingleton.class) {
                if (lazySimpleSingleton == null)
                    lazySimpleSingleton = new LazySimpleSingleton();
            }
        }
        return lazySimpleSingleton;
    }
}

但还是不能保证会获取单例不会被破坏

/**
 * @author DK
 * @version 1.0
 * @date 2020-06-08 10:10
 **/
public class Main {
    public static void main(String[] args) throws Exception{
        LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance();
        //我们还可以通过反射去把单例对象创建出来
        Class<LazySimpleSingleton> lazySimpleSingletonClass = LazySimpleSingleton.class;
        Constructor<? extends LazySimpleSingleton> declaredConstructor = lazySimpleSingletonClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        LazySimpleSingleton lazySimpleSingleton1 = declaredConstructor.newInstance();
        System.out.println(lazySimpleSingleton==lazySimpleSingleton1);//结果是flase
    }
}
/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:06
 */
public class LazySimpleSingleton {
    private volatile static LazySimpleSingleton lazySimpleSingleton;
    private static  boolean initFlag;
    private LazySimpleSingleton(){
        synchronized (this) {
            if (initFlag) {
                throw new RuntimeException("不允许创建多个实例");
            }else {
                initFlag = true;
                lazySimpleSingleton = this;
            }
        }
    }

    public static LazySimpleSingleton getInstance() {
        if (lazySimpleSingleton == null) {
            synchronized (LazySimpleSingleton.class) {
                if (lazySimpleSingleton == null)
                    lazySimpleSingleton = new LazySimpleSingleton();
            }
        }
        return lazySimpleSingleton;
    }
}

还可以通过内部类

/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:06
 */
public class LazySimpleSingleton {
    //默认使用LazySimpleSingleton的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazySimpleSingleton(){
        if (Holder.lazySimpleSingleton != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }
    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static LazySimpleSingleton getInstance() {
        //在返回结果以前,一定会先加载内部类
        return Holder.lazySimpleSingleton;
    }
    //默认不加载
    private static class Holder{
        private static final LazySimpleSingleton lazySimpleSingleton = new LazySimpleSingleton();
    }
}

但还可以通过序列化方式进行破快

/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:06
 */
//要实现序列化结果
public class LazySimpleSingleton implements Serializable{
    //默认使用LazySimpleSingleton的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazySimpleSingleton(){
        if (Holder.lazySimpleSingleton != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }
    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static LazySimpleSingleton getInstance() {
        //在返回结果以前,一定会先加载内部类
        return Holder.lazySimpleSingleton;
    }
    //默认不加载
    private static class Holder{
        private static final LazySimpleSingleton lazySimpleSingleton = new LazySimpleSingleton();
    }
}
/**
 * @author DK
 * @version 1.0
 * @date 2020-06-08 10:10
 **/
public class Main {
    public static void main(String[] args) throws Exception{
        LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(instance);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        LazySimpleSingleton o = (LazySimpleSingleton)objectInputStream.readObject();
        System.out.println(o == instance); //结果还是false
    }
}
/**
 * @author dk
 * @version 1.0
 * @date 2020/6/6 22:06
 */
public class LazySimpleSingleton implements Serializable{
    //默认使用LazySimpleSingleton的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazySimpleSingleton(){
        if (Holder.lazySimpleSingleton != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }
    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static LazySimpleSingleton getInstance() {
        //在返回结果以前,一定会先加载内部类
        return Holder.lazySimpleSingleton;
    }
    //默认不加载
    private static class Holder{
        private static final LazySimpleSingleton lazySimpleSingleton = new LazySimpleSingleton();
    }
	//加上这个方法再试下 这时结果是true的
    public Object readResolve() {
        return Holder.lazySimpleSingleton;
    }
}

为什么加上readResolve 反序列化的对象是原来的对象 先来看下ObjectInputStream readObject 这个方法
在这里插入图片描述

点击32行进去看获取反序列化的方法
在这里插入图片描述
滑到1574 对象类型 点击进去 readOrdinaryObject
在这里插入图片描述

看到2078行这里 有个hasReadResolveMethod 点进去
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

然后跳回desc.hasReadResolveMethod()下面继续看
在这里插入图片描述

    /**
     * 重写 readResolve方法,只不过是覆盖反序列化出来的对象
     * 实际上还是创建了两次,在jvm层面上,相对比较安全
     * 之前反序列化的对象会被gc 回收
     */
    public Object readResolve() {
        return Holder.lazySimpleSingleton;
    }

枚举式单例 如果有看过Effective Java这本书应该知道 也值得去看下

在这里插入图片描述

/**
 * @author DK
 * @version 1.
 * 0
 * @date 2020/6/7 6:44 下午
 */
public enum EnumSingleton {
    Singleton;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
/**
 * @author dk
 * @version 1.0
 * @date 2020/6/8 21:50
 */
public class Main {
    public static void main(String[] args) {
        EnumSingleton enumSingleton = EnumSingleton.Singleton;
        EnumSingleton singleton = EnumSingleton.Singleton;
        System.out.println(enumSingleton == singleton);//true
        enumSingleton.setData(new Object());
        System.out.println(enumSingleton == singleton);//true
    }
}

即使面对反射攻击和复杂序列,也绝对保证多次实例化,为什么呢 ,可以通过jad反编译一下EnumSingleton

//这个就是通过jad 反编译EnumSingleton.class文件的结果 jad可以百度直接下载
public final class EnumSingleton extends Enum
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(singleton/hungry/EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public Object getData()
    {
        return data;
    }

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

    public static final EnumSingleton Singleton;
    private Object data;
    //定义未初始化
    private static final EnumSingleton $VALUES[];
    //static 初始化 属于饿汉式写法,本身线程安全
    static 
    {
        Singleton = new EnumSingleton("Singleton", 0);
        $VALUES = (new EnumSingleton[] {
            Singleton
        });
    }
}
/**
 * @author dk
 * @version 1.0
 * @date 2020/6/8 21:50
 */
public class Main {
    public static void main(String[] args) throws Exception{
        EnumSingleton enumSingleton = EnumSingleton.Singleton;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(enumSingleton);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        EnumSingleton enumSingleton1 = (EnumSingleton) objectInputStream.readObject();
        System.out.println(enumSingleton == enumSingleton1);//true 为什么呢 要去挖一下源码了
    }
}

在这里插入图片描述
这是点击枚举类型 readEnum方法进去

在这里插入图片描述

看下反射能不能破坏,之前我们通过jad看到他私有的构造器是这样的

在这里插入图片描述

/**
 * @author dk
 * @version 1.0
 * @date 2020/6/8 21:50
 */
public class Main {
    public static void main(String[] args) throws Exception{
        EnumSingleton enumSingleton = EnumSingleton.Singleton;
        Class<EnumSingleton> enumSingletonClass = EnumSingleton.class;
        Constructor<EnumSingleton> constructor = enumSingletonClass.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        EnumSingleton singleton = constructor.newInstance("Singleton", 0);
        System.out.println(enumSingleton==singleton); //throw Cannot reflectively create enum objects
    }
}

为什么会报这个错呢,咱们点进去看newInstance 看创建实例过程

在这里插入图片描述

注册式

/**
 * 注册时登记
 * @author DK
 * @version 1.0
 * @date 2020-06-08 14:50
 **/
public class ContainerSingleton {
    //ConcurrentHashMap是线程安全的 采用的是分段锁
    private static final Map<String, Object> LOC = new ConcurrentHashMap<>();

    public static Object getBean(String className) {

        //这里为什么要加synchronized,虽然ConcurrentHashMap是线程安全,但是只是针对他内部, 但getBean这个不是线程安全
        synchronized (LOC) {
            if (LOC.containsKey(className))
                return LOC.get(className);
            else {
                Object object = null;
                try {
                    Class<?> clazz = Class.forName(className);
                    object = clazz.newInstance();
                    LOC.put(className, object);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return object;
            }
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值