JUC-反射与单例模式(十四)

一、饿汉式单例模式:

​ 饿汉式:饥饿的人,看到食物就上来抢。对应在代码中,在类加载时就立刻实例化。

public class HungrySingleton {
   
    //假如类中存在这样的空间开辟的操作:
    //使用饿汉式时,不管用不用,上来就给你开了再说,造成空间浪费。
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    //1、私有构造器
    private HungrySingleton(){

    }
    //2、类的内部创建对象
    private final static HungrySingleton HUNGRYSINGLE = new HungrySingleton();

    //3、向外暴露一个静态的公共方法。 getInstance
    public static HungrySingleton getInstance(){
        return HUNGRYSINGLE;
    }
    public static void main(String[] args) {
        //单线程:
        HungrySingleton instance1 = HungrySingleton.getInstance();
        HungrySingleton instance2 = HungrySingleton.getInstance();
        System.out.println(instance1 == instance2);//true
        System.out.println("------------");
        //多线程:
        new Thread(()->{
            HungrySingleton instance_A = HungrySingleton.getInstance();
            System.out.println(instance_A);
            //com.kuangstudy.Singleton.HungrySingleton@626213bf
        }).start();
        new Thread(()->{
            HungrySingleton instance_B = HungrySingleton.getInstance();
            System.out.println(instance_B);
            //com.kuangstudy.Singleton.HungrySingleton@626213bf
        }).start();
    }
}

优点:

  • 基于 classloader 机制避免了多线程的同步问题,在类装载的时候就完成实例化。避免了线程同步问题==>线程安全。
  • 没有加锁,执行效率会提高。
  • 简单好用。

缺点:

  • 在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

二、懒汉式单例模式:

2.1 非线程安全:

public class LazySingleton {

    //1.私有化构造函数
    private LazySingleton() {
        System.out.println(Thread.currentThread().getName()+" ->OK");
    }

    //2.创建对象(容器)
    private static LazySingleton lazyMan ;

    //3.对外提供静态实例化方法,判断在对象为空的时候创建
    public static LazySingleton getInstance(){
        //用的时候再加载
        if (lazyMan == null) {
            lazyMan = new LazySingleton();
        }
        return lazyMan;
    }
    //测试单线程下懒汉式单例
    public static void main(String[] args) {
        LazySingleton instance1 = LazySingleton.getInstance();
        LazySingleton instance2 = LazySingleton.getInstance();
        //实例化两个只出现:main ->OK 
        System.out.println(instance1);//com.kuangstudy.Singleton.LazySingleton@61bbe9ba
        System.out.println(instance2);//com.kuangstudy.Singleton.LazySingleton@61bbe9ba
    }
}

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class LazySingleton {

    //1.私有化构造函数
    private LazySingleton() {
        System.out.println(Thread.currentThread().getName()+" ->OK");
    }

    //2.创建对象(容器)
    private static LazySingleton lazyMan ;

    //3.对外提供静态实例化方法,判断在对象为空的时候创建
    public static LazySingleton getInstance(){
        //用的时候再加载
        if (lazyMan == null) {
            lazyMan = new LazySingleton();
        }
        return lazyMan;
    }
    //开启多条线程实例化LazySingleton
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //            new Thread(()->{LazySingleton.getInstance();}).start();
            new Thread(LazySingleton::getInstance).start();
        }
    }
    //结果实例化超过1个对象:(不唯一)
    /*

    Thread-0 ->OK
    Thread-3 ->OK
    Thread-2 ->OK
    Thread-1 ->OK

    */
}


2.2 线程安全:

public class LazySingletonWithDCL {

    //1.私有化构造函数
    private LazySingletonWithDCL() {
        System.out.println(Thread.currentThread().getName() + " ->OK");
    }

    //2.创建对象(容器)
    //    private static LazySingletonWithDCL lazyMan ;
    //5.new 不是一个原子性操作:
    private volatile static LazySingletonWithDCL lazyMan;

    //3.对外提供静态实例化方法
    //4.为保证线程安全,需要上锁
    public static LazySingletonWithDCL getInstance() {
        if (lazyMan == null) {
            synchronized (LazySingletonWithDCL.class) {
                if (lazyMan == null) {
                    lazyMan = new LazySingletonWithDCL();
                }
            }
        }
        return lazyMan;
    }

    //开启多条线程实例化LazySingletonWithDCL
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //            new Thread(()->{LazySingletonWithDCL.getInstance();}).start();
            new Thread(LazySingletonWithDCL::getInstance).start();
        }
    }
}

双检锁/双重校验锁(DCL,即 double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。
这样,实例化代码只用执行一次,后面再次访问时,判断 if(lazyMan == null),直接return实例化对象,也避免的反复进行方法同步.
线程安全;延迟加载;效率较高

2.3 问题:

为什么要synchronized (LazySingletonWithDCL.class)而不是对方法加锁?

简单回答:
synchronized 重量级锁,锁的范围越小越好,class只有一个,而方法会每次都执行,因此为了提高效率,锁对象而不是锁方法。

new对象的过程为什么不是一个原子性操作?

实锤 new 对象不是原子性操作,会执行以下操作:
分配内存空间
执行构造方法,初始化对象
把对象指向空间

实践出真知:
1、这是原子类AtomicInteger.getAndIncrement()方法,反编译后:
在这里插入图片描述
2、手动new一个对象

public class TestNew {

    private static int num = 0;

    public static void main(String[] args) {
        TestNew testNew = new TestNew();
    }
    public void add(){
        num++;
    };
}

反编译后:
在这里插入图片描述

三、反射破坏单例:

3.1 反射破坏:对DCL饿汉式单例进行破解

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    LazySingletonWithDCL instance = LazySingletonWithDCL.getInstance();
    //利用反射创建对象
    //获取无参构造
    Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
    //开放权限
    declaredConstructor.setAccessible(true);
    LazySingletonWithDCL instance_withReflect = declaredConstructor.newInstance();

    System.out.println(instance); //LazySingletonWithDCL@61bbe9ba
    System.out.println(instance_withReflect);  //LazySingletonWithDCL@610455d6
    System.out.println(instance == instance_withReflect);  //false
}

3.2 懒汉升级:增加校验反击

因为反射走的是无参构造,可以在构造函数中进行判断

public class LazySingletonWithDCL {

    //1.私有化构造函数
    //6.增加对反射的判断
    private LazySingletonWithDCL() {
        synchronized (LazySingletonWithDCL.class){
            //6.1如果此时已经有实例,阻止反射创建
            if (lazyMan != null){
                throw new RuntimeException("不要试图通过反射破解单例");
            }
        }
        System.out.println(Thread.currentThread().getName() + " ->OK");
    }

    //2.创建对象(容器)
    //    private static LazySingletonWithDCL lazyMan ;
    //5.new 不是一个原子性操作:
    private volatile static LazySingletonWithDCL lazyMan;

    //3.对外提供静态实例化方法
    //4.为保证线程安全,需要上锁
    public static LazySingletonWithDCL getInstance() {
        if (lazyMan == null) {
            synchronized (LazySingletonWithDCL.class) {
                if (lazyMan == null) {
                    lazyMan = new LazySingletonWithDCL();
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        LazySingletonWithDCL instance = LazySingletonWithDCL.getInstance();
        System.out.println(instance); //创建成功: LazySingletonWithDCL@61bbe9ba
        //利用反射创建对象
        //获取无参构造
        Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
        //开放权限
        declaredConstructor.setAccessible(true);
        LazySingletonWithDCL instance_withReflect = declaredConstructor.newInstance();

        System.out.println(instance_withReflect);  //LazySingletonWithDCL@610455d6
        System.out.println(instance == instance_withReflect);  //false
    }
}

main ->OK
com.kuangstudy.Singleton.LazySingletonWithDCL@61bbe9ba
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.kuangstudy.Singleton.LazySingletonWithDCL.main(LazySingletonWithDCL.java:60)
Caused by: java.lang.RuntimeException: 不要试图通过反射破解单例
at com.kuangstudy.Singleton.LazySingletonWithDCL.(LazySingletonWithDCL.java:20)
… 5 more

只创建出了一个实例,并成功拦截了 通过反射创建对象的行为

3.3 反射加大破坏:两个对象都是用反射创建

public static void main(String[] args) throws Exception {
    //获取类模板class
    Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
    //开放权限
    declaredConstructor.setAccessible(true);
    //通过空参创建实例
    LazySingletonWithDCL instance_withReflect1 = declaredConstructor.newInstance();
    LazySingletonWithDCL instance_withReflect2 = declaredConstructor.newInstance();
    System.out.println(instance_withReflect1);//LazySingletonWithDCL@61bbe9ba
    System.out.println(instance_withReflect2);//LazySingletonWithDCL@610455d6
    System.out.println(instance_withReflect1 == instance_withReflect2);//false
}

结果非常明显:再一次破坏了单例模式

3.4 懒汉再度防守:加入标志位

在空参构造方法里运用标志位,因为实例化对象需要获取类模板:

  • 当获取了一次类模板之后,就把标志位flag置反。
  • 等一次通过反射获取类模板创建对象的时候便能抛异常
public class LazySingletonWithDCL {

    //7.加入标志位,防止多个反射破坏单例
    private static boolean flag = true;

    //1.私有化构造函数
    //6.增加对反射的判断
    private LazySingletonWithDCL() {
        synchronized (LazySingletonWithDCL.class){
            if (flag){
                flag = false;
            }else {
                //                //6.1如果此时已经有实例,阻止反射创建
                //                if (lazyMan != null){
                throw new RuntimeException("不要试图通过反射破解单例");
                //                }
            }

        }
        System.out.println(Thread.currentThread().getName() + " ->OK");
    }

    //2.创建对象(容器)
    //    private static LazySingletonWithDCL lazyMan ;
    //5.new 不是一个原子性操作:
    private volatile static LazySingletonWithDCL lazyMan;

    //3.对外提供静态实例化方法
    //4.为保证线程安全,需要上锁
    public static LazySingletonWithDCL getInstance() {
        if (lazyMan == null) {
            synchronized (LazySingletonWithDCL.class) {
                if (lazyMan == null) {
                    lazyMan = new LazySingletonWithDCL();
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        //获取类模板class
        Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
        //开放权限
        declaredConstructor.setAccessible(true);
        //通过空参创建实例
        LazySingletonWithDCL instance_withReflect1 = declaredConstructor.newInstance();
        System.out.println(instance_withReflect1);//LazySingletonWithDCL@61bbe9ba

        LazySingletonWithDCL instance_withReflect2 = declaredConstructor.newInstance();
        System.out.println(instance_withReflect2);
        //报错:java.lang.RuntimeException: 不要试图通过反射破解单例
        System.out.println(instance_withReflect1 == instance_withReflect2);
    }
}

结果:

main ->OK
com.atguigu.test.LazySingletonWithDCL@27ddd392
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.atguigu.test.LazySingletonWithDCL.main(LazySingletonWithDCL.java:54)
Caused by: java.lang.RuntimeException: 不要试图通过反射破解单例
at com.atguigu.test.LazySingletonWithDCL.(LazySingletonWithDCL.java:19)
… 5 more

由运行结果可见,第一次反射创建成功,第二次反射由于标志位的原因报错了,保住了单例模式。至此,用过反射调用空参实例化的方法被标志位掐断

3.5 反射继续破坏:破坏标志位

public static void main(String[] args) throws Exception{
    //获取标志位
    Field flag = LazySingletonWithDCL.class.getDeclaredField("flag");
    flag.setAccessible(true);

    //获取类模板class
    Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
    //开放权限
    declaredConstructor.setAccessible(true);
    //通过空参创建实例
    LazySingletonWithDCL instance_withReflect1 = declaredConstructor.newInstance();
    System.out.println(instance_withReflect1);//LazySingletonWithDCL@61bbe9ba

    //在获取并使用完类模板后,重新设置flag值:
    flag.set(instance_withReflect1,true);
    LazySingletonWithDCL instance_withReflect2 = declaredConstructor.newInstance();
    System.out.println(instance_withReflect2);

}

结果:

main ->OK
com.kuangstudy.Singleton.LazySingletonWithDCL@61bbe9ba
main ->OK
com.kuangstudy.Singleton.LazySingletonWithDCL@511d50c0

3.6 该如何真正确保单例

解铃还须系铃人,查看反射中newInstance()方法
在这里插入图片描述
枚举可以不被反射破坏,构造枚举尝试单例模式:

public enum EnumSingleton {

    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

class Test{

    public static void main(String[] args) {
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        EnumSingleton instance2 = EnumSingleton.INSTANCE;
        System.out.println(instance1.hashCode());//1639705018
        System.out.println(instance2.hashCode());//1639705018
    }
}

3.7 尝试用反射破坏枚举的单例模式

public static void main(String[] args) throws Exception {
    //instance1正常获取
    EnumSingleton instance1 = EnumSingleton.INSTANCE;
    System.out.println(instance1); //INSTANCE

    //instance2通过反射获取:
    //1.获取其空参
    Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(null);
    //2.打开权限:
    constructor.setAccessible(true);
    //3.实例化对象:
    EnumSingleton instance2 = constructor.newInstance();
    System.out.println(instance2);
}

结果:

instance1 正常实例化

但是在通过反射的时候报了错:
在这里插入图片描述

找不到EnumSingleton 的空参构造?

这就奇怪了。

idea查看class文件也是只有空参构造:
在这里插入图片描述
用javap -p -c .\EnumSingleton.class口令也是看到空参:
在这里插入图片描述
探究枚举类的构造函数:
上述方法行不通后得运用更加专业的工具进行反编译:

使用jad工具

使用命令:

 .\jad.exe -sjava .\EnumSingleton.class

将class成功反编译为Java文件:

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingleton.java

package com.kuangstudy.Singleton;


public final class EnumSingleton extends Enum
{

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

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(com/kuangstudy/Singleton/EnumSingleton, name);
    }

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

    public EnumSingleton getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingleton INSTANCE;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

此时我们发现枚举类内部其实是一个有参的构造函数。

此时我们修改代码:

 //1.获取其有参构造:
        Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);

这时候我们就能如愿得到想要的报错了 (?怎么听起来怪怪的)

INSTANCE
Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.kuangstudy.Singleton.Test.main(EnumSingleton.java:40)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值