美文网首页
12、彻底玩转单例模式

12、彻底玩转单例模式

作者: i小雨 | 来源:发表于2020-11-24 09:51 被阅读0次

饿汉式单例:

//饿汉式单例(造成内存资源浪费)
public class HungrySingle {

    private HungrySingle(){

    }

    private final static HungrySingle HUNGRY_SINGLE = new HungrySingle();

    public static HungrySingle getInstance(){
        return HUNGRY_SINGLE;
    }

}

懒汉式单例:

//懒汉式单例(用的时候才去实例化)
public class LazySingle {

    private LazySingle(){

    }
    private static LazySingle lazySingle;

    public static LazySingle getInstance(){
        if (lazySingle==null){
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }
}

问题:

懒汉式在多线程下:

public class LazySingle {

    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"  OK");
    }
    private static LazySingle lazySingle;

    public static LazySingle getInstance(){
        if (lazySingle==null){
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                LazySingle.getInstance();
            }).start();
        }
    }
}

结果:(可能会出现多次创建对象——即和单例相违背)

Thread-0  OK
Thread-1  OK

双重检测锁模式的懒汉式单例(DCL懒汉式):

public class LazySingle {

    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"  OK");
    }
    private static LazySingle lazySingle;

    //双重检测锁模式的懒汉式单例,简称DCL懒汉式
    public static LazySingle getInstance(){
        if (lazySingle==null){
            synchronized (LazySingle.class){
                if (lazySingle==null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                LazySingle.getInstance();
            }).start();
        }
    }
}

结果:(可以发现在这种情况下单例能够保证)

Thread-0  OK

但是,上面的双重检测锁也可能出现问题:
因为new LazySingle();不是一个原子操作
new操作会经过三步:
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
以上三步期望的执行是1、2、3。但是如果发生指令重排,可能编程1、3、2,这就会导致发生错误。
于是在LazySingle前加上volatile

public class LazySingle {

    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"  OK");
    }

    private volatile static LazySingle lazySingle;

    //双重检测锁模式的懒汉式单例,简称DCL懒汉式
    public static LazySingle getInstance(){
        if (lazySingle==null){
            synchronized (LazySingle.class){
                if (lazySingle==null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                LazySingle.getInstance();
            }).start();
        }
    }
}

静态内部类实现的单例

//静态内部类实现的单例
public class InnerSingle {

    private InnerSingle(){

    }

    public static InnerSingle getInstance(){
        return InnerClass.INNER_SINGLE;
    }

    public static class InnerClass{
        private static final InnerSingle INNER_SINGLE = new InnerSingle();
    }

}

但是通过反射技术可以破解以上三种代码

利用反射破坏双重检测锁模式的懒汉式单例

public class TestReflect {

    public static void main(String[] args) throws Exception {
        LazySingle instance1 = LazySingle.getInstance();

        //通过反射创建对象
        Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//暴力反射,强制访问私有构造方法
        LazySingle instance2 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);

    }
}

结果:(可以发现,结果的两个对象不是同一个对象,说明反射对单例模式进行了破坏)

main  OK
main  OK
com.company.single.LazySingle@1b6d3586
com.company.single.LazySingle@4554617c

如何避免通过反射进行破坏:

通过三重检测(即通过标志位在私有构造方法中加上标志判断)

public class LazySingle {

    //利用标志位来进行判断,需要对关键字flag进行加密处理
    private static boolean flag = false;
    private LazySingle(){

        synchronized (LazySingle.class){
            if (flag==false){
                flag = true;
            }else{
                throw new RuntimeException("不要试图通过反射破坏单例的异常");
            }
        }

    }

    private volatile static LazySingle lazySingle;

    //双重检测锁模式的懒汉式单例,简称DCL懒汉式
    public static LazySingle getInstance(){
        if (lazySingle==null){
            synchronized (LazySingle.class){
                if (lazySingle==null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }
}

结果:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.company.single.TestReflect.main(TestReflect.java:14)
Caused by: java.lang.RuntimeException: 不要试图通过反射破坏单例的异常
    at com.company.single.LazySingle.<init>(LazySingle.java:13)
    ... 5 more

但是还是可以用反编译获取flag变量名来进行破坏:

public static void main(String[] args) throws Exception {
        //
        Field flag = LazySingle.class.getDeclaredField("flag");
        flag.setAccessible(true);

        //通过反射创建对象
        Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//暴力反射,强制访问私有构造方法
        LazySingle instance2 = declaredConstructor.newInstance();
        flag.set(instance2,false);
        LazySingle instance3 = declaredConstructor.newInstance();

        System.out.println(instance2);
        System.out.println(instance3);
    }

结果:(可以发现反射又破坏了单例)

com.company.single.LazySingle@74a14482
com.company.single.LazySingle@1540e19d

相关文章

  • 12、彻底玩转单例模式

    饿汉式单例: 懒汉式单例: 问题: 懒汉式在多线程下: 结果:(可能会出现多次创建对象——即和单例相违背) 双重检...

  • 【设计模式】单例模式

    单例模式 常用单例模式: 懒汉单例模式: 静态内部类单例模式: Android Application 中使用单例模式:

  • Android设计模式总结

    单例模式:饿汉单例模式://饿汉单例模式 懒汉单例模式: Double CheckLock(DCL)实现单例 Bu...

  • 2018-04-08php实战设计模式

    一、单例模式 单例模式是最经典的设计模式之一,到底什么是单例?单例模式适用场景是什么?单例模式如何设计?php中单...

  • 设计模式之单例模式详解

    设计模式之单例模式详解 单例模式写法大全,也许有你不知道的写法 导航 引言 什么是单例? 单例模式作用 单例模式的...

  • Telegram开源项目之单例模式

    NotificationCenter的单例模式 NotificationCenter的单例模式分析 这种单例模式是...

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • IOS单例模式的底层原理

    单例介绍 本文源码下载地址 1.什么是单例 说到单例首先要提到单例模式,因为单例模式是单例存在的目的 单例模式是一...

  • 单例

    iOS单例模式iOS之单例模式初探iOS单例详解

  • 单例模式

    单例模式1 单例模式2

网友评论

      本文标题:12、彻底玩转单例模式

      本文链接:https://www.haomeiwen.com/subject/nkpviktx.html