单例模式

2023/6/1 1:25:10

本文主要是介绍单例模式,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

单例模式(反射破坏-枚举)

饿汉式单例

package com.jan.single;

//饿汉式单例
public class Hungry {
    //一上来就会加载好,可能会浪费空间
    private byte[] data11=new byte[1024*1024];
    private byte[] data12=new byte[1024*1024];
    private byte[] data13=new byte[1024*1024];
    private byte[] data14=new byte[1024*1024];

    private Hungry(){

    }

    private final static Hungry HUNGRY = new Hungry();//保证是唯一的了

    public static Hungry getInstance(){ //一上来就加载了
        return HUNGRY;
    }
}

懒汉式单例

package com.jan.single;

//懒汉式单例
public class LazyMan {

    private LazyMan(){ ///构造器私有
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private  static  LazyMan lazyMan;//对象

    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();
        }
    }
}

此时运行结果:单例有问题,偶尔会有多线程

静态内部类:

package com.jan.single;

//静态内部类
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.jan.single;

//懒汉式单例
public class LazyMan {

    private LazyMan(){ ///构造器私有
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    //为了安全,避免 lazyMan 指令重排,必须加volatile
    private volatile static  LazyMan lazyMan;//对象

    public static LazyMan getInstance(){  //这代码安全吗?
        //双重检测锁模式的 懒汉式单例 DCL模式
        if (lazyMan==null){
            synchronized (LazyMan.class){  //保证这个类 LazyMan.class 只有一个
                if(lazyMan==null){
                    lazyMan = new LazyMan(); //不是原子性操作
                    /*
                    * 1. 分配内存空间
                    * 2. 执行构造方法,初始化对象
                    * 3. 把这个对象指向这个空间
                    * 
                    * 期望执行 123
                    *          132 A(先占内存空间)
                    * 若此时有 B线程  基于A,系统会认为 lazyMan 不为Null   直接return
                    *        //此时lazyMan没有完成构造
                    * */
                }
            }
        }
        return lazyMan; 
    }

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

单例不安全,反射

枚举

炫技时刻 :反射破坏单例

package com.jan.single;

import java.lang.reflect.Constructor;

//懒汉式单例
public class LazyMan {

    private LazyMan(){ ///构造器私有
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    //为了安全,避免 lazyMan 指令重排,必须加volatile
    private volatile static  LazyMan lazyMan;//对象

    public static LazyMan getInstance(){  //这代码安全吗?
        //双重检测锁模式的 懒汉式单例 DCL模式
        if (lazyMan==null){
            synchronized (LazyMan.class){  //保证这个类 LazyMan.class 只有一个
                if(lazyMan==null){
                    lazyMan = new LazyMan(); //不是原子性操作
                 
                }

            }

        }
        return lazyMan;
    }

    //反射
    public static void main(String[] args) throws Exception {
        LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
        declaredConstructor.setAccessible(true);//破坏私有权限,无视私有的构造器   通过反射来创建对象
        LazyMan instance2= declaredConstructor.newInstance();

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

结果:

继续改进:不被反射破坏(三重检测)

package com.jan.single;

import java.lang.reflect.Constructor;

//懒汉式单例
public class LazyMan {

    private LazyMan(){ ///构造器私有

        synchronized (LazyMan.class){ //这里加一把锁,第二次用就会报错
            if (lazyMan!=null){
             throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    //为了安全,避免 lazyMan 指令重排,必须加volatile
    private volatile static  LazyMan lazyMan;//对象

    public static LazyMan getInstance(){  //这代码安全吗?
        //双重检测锁模式的 懒汉式单例 DCL模式
        if (lazyMan==null){
            synchronized (LazyMan.class){  //保证这个类 LazyMan.class 只有一个
                if(lazyMan==null){
                    lazyMan = new LazyMan(); //不是原子性操作
                    /*
                    * 1. 分配内存空间
                    * 2. 执行构造方法,初始化对象
                    * 3. 把这个对象指向这个空间
                    *
                    * 期望执行 123
                    *          132 A(先占内存空间)
                    * 若此时有 B线程  基于A,系统会认为 lazyMan 不为Null   直接return
                    *        //此时lazyMan没有完成构造
                    * */
                }

            }

        }
        return lazyMan;
    }

    
    //反射
    public static void main(String[] args) throws Exception {
        LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
        declaredConstructor.setAccessible(true);//破坏私有权限,无视私有的构造器   通过反射来创建对象
        LazyMan instance2= declaredConstructor.newInstance();

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

    }

}

结果:

继续破坏:

如果两个对象都是LazyMan instance2= declaredConstructor.newInstance();这样获得,单例又被破坏

解决:加密(jan)

package com.jan.single;

import java.lang.reflect.Constructor;

//懒汉式单例
public class LazyMan {

    private static boolean jan = false;//定义一个变量


    private LazyMan(){ ///构造器私有
        synchronized (LazyMan.class){ //这里加一把锁,第二次用就会报错
            if (jan==false){
                jan = true; //这个肯定会变
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }

        }
    }

    //为了安全,避免 lazyMan 指令重排,必须加volatile
    private volatile static  LazyMan lazyMan;//对象

    public static LazyMan getInstance(){  //这代码安全吗?
        //双重检测锁模式的 懒汉式单例 DCL模式
        if (lazyMan==null){
            synchronized (LazyMan.class){  //保证这个类 LazyMan.class 只有一个
                if(lazyMan==null){
                    lazyMan = new LazyMan(); //不是原子性操作
                }

            }

        }
        return lazyMan;
    }

    
    //反射
    public static void main(String[] args) throws Exception {
        //LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
        declaredConstructor.setAccessible(true);//破坏私有权限,无视私有的构造器  通过反射来创建对象
        LazyMan instance2= declaredConstructor.newInstance();
        LazyMan instance1= declaredConstructor.newInstance();

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

    }

}

再破坏:

通过获取加密字段,从而破坏私有权限,再设置加密字段.instance = false
package com.jan.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

//懒汉式单例
//道高一尺魔高一丈
public class LazyMan {

    private static boolean jan = false;//定义一个变量


    private LazyMan(){ ///构造器私有
        synchronized (LazyMan.class){ //这里加一把锁,第二次用就会报错
            if (jan==false){
                jan = true; //这个肯定会变
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }

        }
    }

    //为了安全,避免 lazyMan 指令重排,必须加volatile
    private volatile static  LazyMan lazyMan;//对象

    public static LazyMan getInstance(){  //这代码安全吗?
        //双重检测锁模式的 懒汉式单例 DCL模式
        if (lazyMan==null){
            synchronized (LazyMan.class){  //保证这个类 LazyMan.class 只有一个
                if(lazyMan==null){
                    lazyMan = new LazyMan(); //不是原子性操作
                }

            }

        }
        return lazyMan;
    }

    
    //反射
    public static void main(String[] args) throws Exception {
        //LazyMan instance1 =

        Field jan = LazyMan.class.getDeclaredField("jan");//获取字段
        jan.setAccessible(true);//破坏私有权限


        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
        declaredConstructor.setAccessible(true);//破坏私有权限,无视私有的构造器   通过反射来创建对象
        LazyMan instance2= declaredConstructor.newInstance();

        jan.set(instance2,false);
        LazyMan instance1= declaredConstructor.newInstance();

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

    }

}

结果:

万恶的反射怎么解决:枚举

反编译jad:https://varaneckas.com/jad/

package com.jan.single;

//枚举 是一个什么? 本身也是class 类
public enum EnumSingle {
    
    INSTANCE;//不可能被拿走
    
    public EnumSingle getInstance(){
        return INSTANCE;
    }
    
}

class Test{  //先保证对象是唯一的,测试一下

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

结果:

看源码,有一个无参构造

在测试也使用无参构造

package com.jan.single;
import java.lang.reflect.Constructor;

//枚举 是一个什么? 本身也是class 类
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(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        //com.jan.single.EnumSingle.<init>()
        //(newInstance 源码)正常报错是:throw new IllegalArgumentException("Cannot reflectively create enum objects");
       
        System.out.println(instance1);
        System.out.println(instance2);

    }
}

结果:被欺骗,这源码不是真的

因为newinstance的源码:

两个输出的结果不同!!!

反编译

在IDEA的targer中找到 EnumSingle.class,打开文件的文件的位置,再从文件的卫视输入 cmd

也有空参,也是骗我们的

更专业的反编译

jad

这里是有参构造

修改代码,这里只展示不同的

Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);

结果:这个反射的确不能破坏枚举的单例

枚举最终反编译源码

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

package com.jan.single;


public final class EnumSingle extends Enum
{

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

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/jan/single/EnumSingle, name);
    }

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

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

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

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


这篇关于单例模式的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程