我是如何理解单例模式(小白理解)

发布于:2023-01-09 ⋅ 阅读:(576) ⋅ 点赞:(0)

        单例模式是软件设计模式中最常用的一种设计模式,他的理解为一个类只能存在一个实例的存在。

        单例设计模式通常可以有多种实现方法,最常使用的是饿汉式即立即加载,与懒汉式(懒加载)两种方法。

        实现思路:就是构造方法私有,其他类就不能直接通过new关键字去创建该类的对象,提供一个静态方法用于创建该类的唯一对象。

        首先先说说饿汉式单例模式,所谓饿汉式,用大白话说就是很饿,看见什么都想吃,可以这样子形容他。在Java中的表现,就是类加载完,此类的对象就会被立即加载到堆中。

public class Single {

    private static Single single=new Single();

    private Single(){}

    public static Single getInstance(){
        return single;
    }
}

        这种模式的弊端,会浪费堆的空间,因为没有使用到该类的对象,如果是加载大量的资源,就会造成资源的过度浪费。所以懒汉式是在这个基础上进行优化。不会一开始就加载,是该对象什么时候被使用到才加载。

懒汉式单例模式

        这种模式作用是延迟加载,懒加载,都可以这样子理解。

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){
        System.out.println(Thread.currentThread().getName());
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

        但是这个代码有一个问题,这种情况下只能是在单线程的时候安全,如果是多线程的情况下,会出现多个实例的可能。因为没有锁的情况下,一个线程在if (lazyMan==null) 这个判断的时候,其他线程也可能在进行判断,从而导致多个实例的情况。

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){
        System.out.println(Thread.currentThread().getName());
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
    //并发情况下,会存在多个实例
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

并发情况下,测试如下

 解决方法,加锁,加了锁之后,只要一个线程获取到该对象的锁,其他线程就获取不到,就只能等,等获取锁的线程释放锁之后,其他线程才能获取锁,再去判断该对象是否存在。

双重检测机制

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){
        System.out.println(Thread.currentThread().getName());
    }
    public static LazyMan getInstance(){
        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.getInstance();
            }).start();
        }
    }
}

这样子只是解决了多线程的时候,实例的唯一性,但是还可以通过反射破坏这种单例模式。

先通过 LazyMan.class.getDeclaredConstructor 获取构造器,再取消其安全检查,然后通过构造器

newInstance()创建该类的实例。
public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){     //加锁
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
    //通过反射
    public static void main(String[] args) throws Exception {
        LazyMan lazyMan = LazyMan.getInstance(); //先通过该类提供的静态方法获取该实例
        //再通过反射获取该实例,单例模式再一次被破坏
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan1);
    }
}

此时第一次是通过类的静态方法创建实例,再通过反射创建第二次实例对象。

我们可以在构造器中加锁,判断当前类是否已经存在实例了,就不能通过反射破坏了,但是还是可以通过两次反射创建两个实例。

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要试图使用反射破坏单例");
            }
        }
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){     //加锁
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
    //通过反射
    public static void main(String[] args) throws Exception {
        //LazyMan lazyMan = LazyMan.getInstance(); //先通过该类提供的静态方法获取该实例

        //再通过反射获取该实例,单例模式再一次被破坏
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan = declaredConstructor.newInstance();
        LazyMan lazyMan1 = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan1);
    }
}

因为两次反射已经绕过了构造器的锁,synchronized (LazyMan.class),所以可以成功创建两个实例,单例模式再次被破坏。

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要试图使用反射破坏单例");
            }
        }
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){     //加锁
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
    //通过反射
    public static void main(String[] args) throws Exception {
        //LazyMan lazyMan = LazyMan.getInstance(); //先通过该类提供的静态方法获取该实例

        //再通过反射获取该实例,单例模式再一次被破坏
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        //两次反射
        LazyMan lazyMan = declaredConstructor.newInstance();
        LazyMan lazyMan1 = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan1);
    }
}

解决方法,可以加一个标志位,来防止两次反射创建两个实例。

public class LazyMan {
    private static LazyMan lazyMan;

    private static boolean xie=false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (xie==false){
                xie=true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏单例");}
        }
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){     //加锁
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
    //通过反射
    public static void main(String[] args) throws Exception {
        //LazyMan lazyMan = LazyMan.getInstance(); //先通过该类提供的静态方法获取该实例

        //再通过反射获取该实例,单例模式再一次被破坏
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        //两次反射
        LazyMan lazyMan = declaredConstructor.newInstance();
        LazyMan lazyMan1 = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan1);
    }
}

但是依旧可以通过反射获取你的标志位,但是你得提前知道这个标志位是什么才可以。

public class LazyMan {
    private static LazyMan lazyMan;

    private static boolean xie=false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (xie==false){
                xie=true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏单例");}
        }
    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){     //加锁
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
    //通过反射
    public static void main(String[] args) throws Exception {
        //LazyMan lazyMan = LazyMan.getInstance(); //先通过该类提供的静态方法获取该实例

        //再通过反射获取该实例,单例模式再一次被破坏
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Field xie = LazyMan.class.getDeclaredField("xie");
        xie.setAccessible(true);
        //两次反射
        LazyMan lazyMan = declaredConstructor.newInstance();
        
        xie.set(lazyMan,false);

        LazyMan lazyMan1 = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan1);
    }
}

所谓道高一尺魔高一丈,总有一些办法可以破坏单例模式,有没有不能被破坏的单例模式呢?

答案就是枚举类的对象。不可以被破坏,即使通过反编译获取到枚举的源码,知道他的getInstance(String s,int i)方法需要两个参数,理论上我们知道了方法以及方法的参数可以通过反射创建该类的实例,但是,通过反射创建枚举的多个实例会抛出异常。

public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;

        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance();

        System.out.println(enumSingle);
    }
}

强行通过反射创建实例,会出现这个异常,意思是,不能通过反射创建枚举的对象。

记录个人笔记,欢迎指正。


网站公告

今日签到

点亮在社区的每一天
去签到