简单总结单例模式和反射

目录

 

恶汉式单例模式

懒汉式单例模式(重点)

静态内部类单例模式

枚举类

idea自带的反编译工具

反射(Reflection)

反射相关的API

获取Class实例的三种方式

哪些类型有Class对象

什么时候会发生类的初始化

类的主动引用

类的被动引用

类加载器的作用

依靠反射动态的获取对象

setAccesssible的作用


恶汉式单例模式

只要是单例模式,先进行构造器的私有化,将无参构造方法加private进行私有

恶汉式是不管需不需要直接生成对象和开辟属性空间,容易造成资源的浪费

​
  package com.ujiuye.single;
​
//恶汉式单例
public class Hungry {
    //恶汉式会容易浪费空间,不管什么直接全部加载会造成资源的浪费
    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];
    //最重要的思想是,构造器私有
    private Hungry(){
​
    }
    //恶汉式是一上来就先把对象创建
    private final static Hungry HUNGRY = new Hungry();
    public static Hungry getInstance(){
        return HUNGRY;
    }
}
​

懒汉式单例模式(重点)

懒汉式正常情况下会经过判断进行生成对象,但当遇到多线程的时候会出现问题,通过synchronized加上类锁(成为DCL懒汉式)之后虽然能正常使用,但是在new lazyMan对象的这个过程其实会出现原子性的问题,就是会发生指令的重排序问题,所以可以在创建对象的时候加上volatile关键字进行防止重排序

volatile在Java并发编程中常用于保持内存可见性和防止指令重排序。内存可见性(Memory Visibility):所有线程都能看到共享内存的最新状态;防止指令重排:在基于偏序关系的Happens-Before内存模型中,指令重排技术大大提高了程序执行效率,但同时也引入了一些问题。

​
  package com.ujiuye.single;
​
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
​
//懒汉式单例
public class LazyMan {
​
    private static boolean wangjiacheng = false;
​
    //先构造器私有,不让别人new对象
    private LazyMan(){
​
        //为了解决反射的破坏需要加一把锁
        //但是当,创建的两个对象都是是用反射来做,就又会被破坏
        synchronized (LazyMan.class){
            //通过加一个关键字 可以解决反射问题
            if (wangjiacheng==false){
                wangjiacheng=true;
            }else {
                throw new RuntimeException("不要试图用使用反射破坏异常");
            }
​
        }
        System.out.println(Thread.currentThread().getName()+"线程启动");
    }
    //为了解决原子性的问题要加上volatile这个关键字
    private volatile static LazyMan lazyMan;
​
    //双重检测锁模式的懒汉式单例,简称 DCL懒汉式
    public static LazyMan getInstance(){
        //通过加类锁来解决多线程问题
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    //如果对象为空,则new对象
                    //但是这一步还是有问题,不是原子性操作
                    /*
                    * 会经过三步
                    * 1.分配内存空间
                    * 2.执行构造方法,初始化对象
                    * 3.把这个对象指向这个空间
                    * 正常情况是123
                    * 但是可能会132
                    * 当A线程完成了132,实际上空间里并没有值,当下一个B线程到来后会认为空间里已经有值了,会直接
                    * return这个lazyMan,返回一个空值
                    * */
                    lazyMan = new LazyMan();
                }
​
            }
​
        }
        return lazyMan;
    }
//    //单线程下可以实现,但是多线程并发下会出现问题,会多次执行,需要加锁
//    public static void main(String []args){
//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//                LazyMan.getInstance();
//            }).start();
//        }
//    }
​
​
​
    //反射
    public static void main(String[]args) throws Exception {
        //LazyMan instance = LazyMan.getInstance();
​
        //继续破坏关键字,来测试
        Field wangjiacheng = LazyMan.class.getDeclaredField("wangjiacheng");
​
        wangjiacheng.setAccessible(true);
​
        //空参构造器
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(String.class,int.class);
        //设置权限,无视private私有的构造器
        declaredConstructor.setAccessible(true);
        //通过反射创建对象
        LazyMan instance1 = declaredConstructor.newInstance();
        wangjiacheng.set(instance1,false);
        LazyMan instance2 = declaredConstructor.newInstance();
        //System.out.println(instance);
        System.out.println(instance1);
        System.out.println(instance2);
​
    }
}
​

静态内部类单例模式

​
package com.ujiuye.single;
​
import java.lang.reflect.Constructor;
​
//静态内部类单例模式
public class Holder {
​
    //只要是单例模式都需要构造器私有
    private Holder(){
​
    }
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
​
    //创建静态内部类
    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
​
}
​

单例模式虽然可以通过加锁和私有构造器的方法来实现多重保护,但是由于反射的存在,还是会不安全,所以又了枚举的产生

枚举类

package com.ujiuye.single;
​
import java.lang.reflect.Constructor;
​
//枚举类 jdk1.5之后出现的,本身也是一个Class类
public enum  EnumSingle {
​
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws Exception{
        EnumSingle instance = EnumSingle.INSTANCE;
        EnumSingle instance1 = EnumSingle.INSTANCE;
        System.out.println(instance.equals(instance1));
        //可以看出枚举是一个自带单例模式的结构,如果通过反射来破坏它呢?
        //先拿到他的class,测试一下他的无参构造方法
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        //破坏他的私有权限
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        //但是居然报错,说明枚举类里面没有这个空参构造方法
        //Exception in thread "main" java.lang.NoSuchMethodException
        System.out.println(instance1.equals(instance2));
    }
}
​

通过我的测试发现,枚举类可以避免反射带来的问题,比较安全,但是枚举的源码显示提供了无参构造方法,但经过我的测试我发现其实并没有,反编译后发现提供了有参构造方法,两个参数分别为String和int

idea自带的反编译工具

windows系统可以直接win+R打开dos窗口

输入javap -p -类名.class进行反编译

MAC电脑需要通过终端先cd进入目标类的文件夹下,然后通过javac 类名.java进行编译

然后通过javap -p -类名.class进行反编译

setAccessible

这个关键字可以破坏private构造器的私有权限,反射来破坏我们的单例模式

反射(Reflection)

 

通过反射可以获取任何类的内部信息,并能直接操作任何对象的内部属性和方法

类加载完成之后,在堆内存的方法区里会产生一个Class类型的对象,并且一个类只有一个Class类对象,这个对象中包含了这个类里的完整的结构信息,我们可以通过这个对象看到类的结构,这就是反射的含义

反射相关的API

java.lang.Class 代表一个类

java.lang.reflect.Method 代表类的方法

java.lang.reflect.Field 代表类的成员变量

java.lang.reflect.Constructor 代表类的构造方法

等等,先掌握这四个就够用了

 

反射之前要先获取Class的对象

获取Class实例的三种方式

1.通过类名.class的方式,最安全常用

2.通过对象.getClass()方法

3.通过Class类的静态方法forName()获取,可能会抛出一个异常

class.forName(类的全路径);

4.基本类型的包装类都有一个Type属性

例如:Integer.TYPE;也是返回一个Class对象,是int

5.获取类的父类类型

Class实例.getSuperClass()这个方法可以获取Class实例的父类类型

哪些类型有Class对象

1.class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类;

2.interface:接口

3.[]:数组

4.enum:枚举

5.annotation:注解@interface

6.primitive type:基本数据类型

7.void

首先执行方法区里的东西,执行的时候会创建一个Class对象放入堆中,然后进行栈中的main方法,会对变量进行一个初始化赋值,然后会new 对象,放入堆中,这个对象会去找他的Class对象,然后Class对象会重新给这个变量赋值,返回到栈中

什么时候会发生类的初始化

类的主动引用

类的主动引用一定会发生类的初始化,有以下几种情况

1.当虚拟机启动,先初始化main方法所在的类

2.new一个类的对象

3.调用类的静态成员(除了final定义的),静态方法

4.使用java.lang.reflect包的方法对类进行反射

5.当初始化一个类,如果他的父类没有被初始化,则会先初始化他的父类

类的被动引用

类的被动引用不会发生类的初始化,有以下几种情况

1.当访问一个静态域的时候,只有真正声明这个域的类才会被初始化,如当通过子类调用父类的静态变量, 不会导致子类初始化

2.通过数组定义类引用,不会出发类的初始化

3.引用常量不会触发类的初始化

类加载器的作用

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

常见的几种类加载器

 

package com.ujiuye.single;
​
public class ClassLoaderDemo {
    public static void main(String[] args) throws Exception{
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //获取系统类加载器的父类加载器-->扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        //获取扩展类加载器-->根加载器(C/C++能获取)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
        //测试当前类是哪个加载器
        ClassLoader classLoader = Class.forName("com.ujiuye.single.ClassLoaderDemo").getClassLoader();
        System.out.println(classLoader);
        //测试jdk内部的类是谁加载的,根加载器加载的,底层用C写的,所以找不到为null
        ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader1);
        //如何获取系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));
    }
​
}
​

依靠反射动态的获取对象

通过newInstance方法获取对象

先获得类的Class对象,通过对象.newInstance()方法可以创建一个对象

1.本质上是调用了类的无参构造器

2.类的构造器的访问权限要足够

通过构造器创建对象

首先获取类的Class对象,通过对象.getDeclaredConstructor(参数类型.class)方法可以获取类的构造器

然后通过构造器.newInstance(参数)方法

invoke方法的作用是激活有两个参数

先通过class对象拿到方法

Method method= class对象.getMethod("方法名字","参数的.class")

然后method.invoke("用这个方法的对象","方法参数赋的值");

setAccesssible的作用

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值