• 深度解析单例模式


    饥汉模式

    package com.cz.single;
    
    /**
     * @author 卓亦苇
     * @version 1.0
     * 2023/3/11 21:31
     */
    public class Hungry {
    
        private byte[] data1 = new byte[1024];
        private byte[] data2 = new byte[1024];
        private byte[] data3 = new byte[1024];
        private byte[] data4 = new byte[1024];
        private Hungry(){
    
        }
    
        private final static Hungry hungry = new Hungry();
        public static Hungry getInstance(){
            return hungry;
        }
    
        public static void main(String[] args) {
         .   Hungry.getInstance();
        }
    }
    

    会浪费内存,执行代码,其4个对象已经被创建,浪费空间。

    懒汉式单例

    package com.cz.single;
    
    /**
     * @author 卓亦苇
     * @version 1.0
     * 2023/3/11 21:35
     */
    public class LazyMan {
    
        private LazyMan(){
            System.out.println(Thread.currentThread().getName()+"  OK");
        }
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getLazyMan(){
            if ((lazyMan==null)){
                synchronized (LazyMan.class){
                    if (lazyMan==null){
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    
        public static void main(String[] args) {
    
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    LazyMan.getLazyMan();
                }).start();
            }
        }
    }
    

    懒汉式为使用该对象才创建新对象,但是初始代码有问题,单线程初始没有问题,多线程会造成,非单例。

    解决办法,首先加锁,先判断对象是否为空,如果为空则将class对象进行上锁,然后需再判断,锁是否为空,如果为空再创建新对象。

    同步代码块简单来说就是将一段代码用一把锁给锁起来, 只有获得了这把锁的线程才访问, 并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码。第二层,是因为使用同步代码块才加上的,有的可能过了第一个if,没到同步代码块

    为双层检测的懒汉式单例,也称DCL懒汉式

    第二个问题

    lazyMan = new LazyMan();
    

    代码为非原子性操作

    创建新对象的底层操作分为3步

    1.分配内存空间
    2、执行构造方法,初始化对象
    3、把这个对象指向这个空间
    但如果不是原子操作,那132的状况式可能发现的,如果在A还没完成构造是,线程B进来,则不会执行if语句,发生错误

    让lazyMan加上volatile参数

    反射会破坏单例模式

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    //        for (int i = 0; i < 10; i++) {
    //            new Thread(()->{
    //                LazyMan.getLazyMan();
    //            }).start();
    //        }
            LazyMan lazyMan1 = LazyMan.getLazyMan();
            //获得空参构造器
            Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            //反射的setAccessible(true)参数,无视私有构造器
            declaredConstructor.setAccessible(true);
            //通过反射建造对象
            LazyMan lazyMan2 = declaredConstructor.newInstance();
    
            System.out.println(lazyMan1);
            System.out.println(lazyMan2);
        }
    

    image-20230314155639972

    解决办法,在无参构造器添加异常

    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要试图反射破坏");
            }
        }
        System.out.println(Thread.currentThread().getName()+"  OK");
    }
    

    image-20230314160149602

    但依然存在问题, if (lazyMan!=null)为非原子性操作,依然存在两个反射对象导致出现非单例的状况

    //可以采用红绿灯解决,定义一个密钥
    private static boolean key = false;
    private LazyMan(){
        synchronized (LazyMan.class){
            if (key==false){
                key=true;
            }else {
    
                    throw new RuntimeException("不要试图反射破坏");
                 
            }
        }
        System.out.println(Thread.currentThread().getName()+"  OK");
    }
    

    但是如果仍然用反射破环,假设获取到了密钥的情况

    //通过反射修改静态参数
    Field key1 = LazyMan.class.getDeclaredField("key");
    key1.setAccessible(true);
    
    //获得空参构造器
    Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    //反射的setAccessible(true)参数,无视私有构造器
    declaredConstructor.setAccessible(true);
    //通过反射建造对象
    LazyMan lazyMan1 = declaredConstructor.newInstance();
    
    //修改对象的密钥参数
    key1.set(lazyMan1,false);
    
    LazyMan lazyMan2 = declaredConstructor.newInstance();
    
    System.out.println(lazyMan1);
    System.out.println(lazyMan2);
    

    单例仍然会被破坏

    真实有效的方式枚举

    package com.cz.single;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    /**
     * @author 卓亦苇
     * @version 1.0
     * 2023/3/14 16:26
     */
    public enum EnumSingle {
    
        INSTANCE;
    
        public EnumSingle getInstance(){
            return INSTANCE;
        }
    }
    class Test{
        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
            EnumSingle instance1 = EnumSingle.INSTANCE;
            //EnumSingle instance2 = EnumSingle.INSTANCE;
            Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
            declaredConstructor.setAccessible(true);
            EnumSingle instance2 = declaredConstructor.newInstance();
    
            System.out.println(instance1);
            System.out.println(instance2);
        }
    }
    

    image-20230314163414487

    至此才得以真正解决


    __EOF__

  • 本文作者: 卓亦苇
  • 本文链接: https://www.cnblogs.com/zhuoblog/p/17215471.html
  • 关于博主: Your encouragement is my biggest motivation
  • 版权声明: ©卓亦苇
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    Wireshark TS | 网络路径不一致传输丢包问题
    Python中的super函数,你熟吗?
    【多线程案例】Java实现简单定时器(Timer)
    python17(pygame)
    cookie和session
    MongoDB数据库网站网页实例-编程语言Python+Django
    内核双链表篇:list.h——list_for_each宏与list_for_each_safe宏(遍历链表)
    毅速课堂:3D打印随形水路在小零件注塑中优势明显
    正则表达式的修饰符
    7 8051 C51 使用_getkey对scanf输入重定向,实现标准输入输出,getchar
  • 原文地址:https://www.cnblogs.com/zhuoblog/p/17215471.html