单例模式--反射破环单例--预防反射破环单例--1.设置标记位--2.枚举单例

单例模式

学习目标:

           1.单例的概念和作用

           2.实现单例的五种方式

           3.各种单例的优缺点分析

           4.了解反射破环单例

           5.防止反射破环单例

这篇文章看完,在单例问题上足够应付多数面试单例问题。

       单例作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

       详解:前半句话的意思是把一个类设置为Static,并且把类设置为私有。这样别人无法new这个类,保证只有一个实例。后半句话给该类设置一个public get()方法。这样我们就可以拿到这个实例。

       场景:windows的任务管理器和windows的回收站,他们在计算机上只能打开一个窗口。

                  读取配置文件的类,读一次加载到文件内存中,我们直接使用就可以了。

                  数据库连接池的设计,在Spring在,每个Bean默认都是单例的。

       优点:单例模式只生成一个实例,减少了系统性能开销。

                  单例模式可以在系统中设置全局访问点,优化共享资源访问。

       常见的五种实现方式:

                  1.饿汉式(线程安全,调用效率高,不能延时加载)

                  2.懒汉式(线程安全,调用效率不高,可以延时加载)

                  3.DCL懒汉式,双重检锁(由于JVM底层内部模型原因,偶尔会出现问题,不建议使用)

                  4.饿汉式改进:静态内部类式(线程安全,调用效率高,可以延时加载)

                  5.枚举单例(线程安全,调用效率高,不能延时加载)

类装载发生的时机:

1.实例化对象时,就像spring管理的bean一样,在tomcat启动时就实例化了bean,那么这个对象bean的类就加载了。

2.通过类名调用静态变量的时候(类名.class除外)

static修饰的变量和方法理解:

1、被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个类来

2、被static修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要new出一个类来

被static修饰的变量、被static修饰的方法统一属于类的静态资源,是类实例之间共享的.

饿汉式单例

//饿汉式单例
public class singleton {
    //私有化构造器
    private singleton(){
    }
    //类初始化的时候,立即加载该对象。
    //static变量会在类装载的时候就初始化,不会引发并发问题。
    //static变量不依赖singleton类特定的实例,被singleton类的所有实例共享。
    //只要singleton类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们
    private static singleton instance = new singleton();
    //提供获取该对象的方法,没有synchronized,效率高。
    //用public修饰的static成员变量和成员方法本质是全局变量和全局方法
    public static singleton getInstance(){
      return instance;
    }
}
class singletonTest{
    public static void main(String[] args) {
        singleton instance = singleton.getInstance();
        singleton instance1 = singleton.getInstance();
        System.out.println(instance==instance1);
    }
}

测试结果

饿汉式单例弊端

private static singleton instance = new singleton();这段代码说明用Static修饰的singleton变量,在类加载的时候就会变成静态资源,而且实例化了singleton类。如果我们在这里给它开辟了空间。就造成了资源浪费。我们利用懒汉式来解决该问题。

懒汉式单例

synchronized关键字修饰说明:
    假如有多个线程同时访问。如果在不加synchronized关键字的时候,线程A去判断instance是否为空,如果为空,则去实例化这个singleton类。就在此刻线程B进来,它也判断instance变量为空,则也去实例化。无法实现单例。所以使用synchronized关键字,让线程排队去访问该方法。有synchronized关键字,效率低。
//懒汉式单例
public class singleton {

    private singleton(){

    }
    //类初始化的时候,不立即加载该对象
    private static singleton instance;
    //synchronized 同步该方法。防止线程不安全,效率较低。
    public static synchronized singleton getInstance(){
        if(instance==null){
            instance = new singleton();
        }
        return instance;
    }
}

测试结果

                          类初始化                                       线程安全                            效率                    弊端

饿汉式               加载类对象,无并发。                   安全                                  高                        浪费资源空间

懒汉式               不加载                                            安全(synchronized)      低                        效率低

懒汉式弊端:

懒汉式单例保证了懒加载和线程安全问题,但是使用了synchronized关键字,效率比较低。需要优化synchronized关键字。所以出现新的单例模式--双重检测单例(DCL懒汉式)

双重检测单例

不需要同步整个方法,锁的范围更精细。如果线程A刚进来发现instance对象没创建,它要和其他线程竞争本类的锁,获得锁之后线程A才会执行里面的代码,这时候再次检测,instance确实为空,说明自己是第一个竞争到这个锁的,这个线程A就负责创建这个对象了,如果检测不是null了,说明这份锁已经被别人用过了,对象也已经创建出来了,以后创建对象直接用就好了。线程A创建实例后直接释放锁,并返回实例对象。

volatile作用:

禁止进行指令重排序。

可见性

DCL懒汉式:通过volatile关键字确保线程安全。

第二次检测后,去创建对象时,对象实例化过程非原子的。

1.在内存中开辟一片内存区域。

2.在这片区域中执行构造函数,实例化对象。

3.instance引用指向这片内存区域。

假设instance变量没有用volatile关键字修饰,A,B俩线程并发访问singleton类中getInstance()方法,线程A先执行,假设它拿到锁对象,并且判断二次检测为空,则实例化这个对象。上述三步操作可能被指令重排序为1-->3-->2。当线程A执行了3,还未执行2,或者2未完全执行完,此时instance不再是null(instance所指向区域已分配内存),若此时轮到了线程B去拿实例,在第一次判断instance,这是非null.就将instance返回了。此时instance对象并未实例化或者并未完全实例化,必将发生错误。

如果instance变量被volatile修饰,俩层效果。

1.禁止指令重排序优化,即不会出现指令未1->3->2的情况,只能是1->2->3.当线程B执行第一次检测判断,instance要么为null,要么指向已完全初始化了的对象。

2.保证instance变量的可见性,当线程A成功初始化实例后,会将实例从线程A工作内存区域刷新到内存。同时其他线程工作内存区域中的instance无效化,使得其他线程只能从主内存中获取instance对象,

详情原因:https://blog.csdn.net/csdnwgf/article/details/96126654

//双重检测(DCL)懒汉式加载
public class singleton {
    private singleton(){

    }
    //类初始化的时候,不立即加载该对象
    private volatile static singleton = instance;
    //synchronized 同步该代码块。锁的更加精细化
    public static  singleton getInstance(){
        if(instance==null){
            synchronized(singleton.class){
                if(instance==null){
                    instance = new singleton();
                }
            }
        }
        return instance;
    }
}

静态内部类单例:

//静态内部类实现
public class singleton {
    private singleton(){
    }
    //创建私有的静态内部类
    //我们在静态内部类中实例化对象,如果我们没有调用getInstance()方法,它这里不会被加载的。
    //这种方法线程安全,因为instance被static finnal修饰,保证了内存中只有一个实例的存在
    //兼容了并发下的高效率调用,解决了延迟加载
    private static class InnerClass{
        private static final singleton instance = new singleton();
    }
    public static singleton getInstance(){
        return InnerClass.instance;
    }
}
class singletonTest{
    public static void main(String[] args) {
        singleton instance = singleton.getInstance();
        singleton instance1 = singleton.getInstance();
        System.out.println(instance==instance1);
    }
}

测试结果:

利用反射破环静态内部类

利用反射拿出它的无参构造方法,破环掉无参构造的private修饰符,利用无参构造对象.newInstance()获取其对象。通过这个对象和正常从静态内部法单例中获取的对象进行hashCode比较。

public class singleton {
    private singleton(){
    }
    //创建私有的静态内部类
    //我们在静态内部类中实例化对象,如果我们没有调用getInstance()方法,它这里不会被加载的。
    //这种方法线程安全,因为instance被static finnal修饰,保证了内存中只有一个实例的存在
    //兼容了并发下的高效率调用,解决了延迟加载
    private static class InnerClass{
        private static final singleton instance = new singleton();
    }
    public static singleton getInstance(){
        return InnerClass.instance;
    }
}
class singletonTest{
    public static void main(String[] args) throws Exception{
        singleton instance = singleton.getInstance();
        Constructor<singleton> declaredConstructor = singleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        singleton singleton = declaredConstructor.newInstance();
        System.out.println(instance == singleton );
        System.out.println(instance.hashCode());
        System.out.println(instance.hashCode());

    }
}

演示结果

设置标记位解决反射破环单例问题

这段代码非常有趣,我们需要很明确的理解到new对象,就需要调用无参构造。使用静态内部类方法,它只会进行一次new对象,以后就是直接返回instance,标记位flag设置在无参构造中,有效保证了实例只能被new一次。

public class singleton {
    private static Boolean flag = false;
    private singleton(){
        if(flag==false){
            flag=true;
        }else {
            throw new RuntimeException("不要试图用反射破环我们的单例");
        }
    }
    //创建私有的静态内部类
    //我们在静态内部类中实例化对象,如果我们没有调用getInstance()方法,它这里不会被加载的。
    //这种方法线程安全,因为instance被static finnal修饰,保证了内存中只有一个实例的存在
    //兼容了并发下的高效率调用,解决了延迟加载
    private static class InnerClass{
        private static final singleton instance = new singleton();
    }
    public static singleton getInstance(){
        return InnerClass.instance;
    }
}
class singletonTest{
    public static void main(String[] args) throws Exception{
        singleton instance = singleton.getInstance();
//        singleton instance1 = singleton.getInstance();
        Constructor<singleton> declaredConstructor = singleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        singleton singleton = declaredConstructor.newInstance();
//        Constructor<singleton> declaredConstructor1 = singleton.class.getDeclaredConstructor();
//        declaredConstructor1.setAccessible(true);
//        singleton singleton1 = declaredConstructor1.newInstance();
        System.out.println(instance == singleton );
        System.out.println(instance.hashCode());
        System.out.println(singleton.hashCode());

    }
}

测试结果

枚举单例

public enum singleton {
    INSTANCE;
    public static singleton getInstance(){
        return INSTANCE;
    }
    public static void main(String[] args) {
        singleton instance = singleton.getInstance();
        singleton instance1 = singleton.getInstance();
        System.out.println(instance==instance1);

    }
}

测试结果

总结常见的五种实现方式:

                  1.饿汉式(线程安全,调用效率高,不能延时加载)

                     饿汉式单例里面没有开辟大量空间,可以用这种方式。

                  2.懒汉式(线程安全,调用效率不高,可以延时加载)

                     假设单例中开辟很多内存空间,因为它可以延时加载。可以使用这种模式。但是效率较低。

                  3.DCL懒汉式,双重检锁(由于JVM底层内部模型原因,偶尔会出现问题,不建议使用)

                     优化了懒汉式同步方法,把它变为同步块,加volatile关键字,保证它的原子性,有序性,一致性。

                  4.饿汉式改进:静态内部类式(线程安全,调用效率高,可以延时加载)

                     静态内部类单例最优秀的单例,兼容了并发下的高效率调用,解决了延时加载,但以上四种都可以被反射破环。

                  5.枚举单例(线程安全,调用效率高,不能延时加载)

                     枚举单例式为纯天然单例,而且反射不能破环它,因为一用,程序就抛异常了。但是它不能延时加载。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值