栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA设计模式之单例模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA设计模式之单例模式

世界上只有两种人,懒人和漫无目的的人,作为一名工程师如果不勤奋且没有目标,淘汰是必然的
学无止境 点我 跳转到Java工程师必备技术栈

单例模式

一、简介二、类型三、饿汉模式

3.1、正常创建3.2、静态内部类 四、懒汉模式

4.1、单线程使用4.2、双重检测锁(DCL)+原子性操作 五、反射与单例的斗争

5.1、反射破坏单例5.2、私有构造加锁--三重检测锁5.3、反射创建对象5.4、私有构造加标记判断5.5、若标记被找到 六、枚举(无法被反射破坏)

6.1、使用6.2、验证

一、简介

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。
在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升
单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

二、类型

饿汉式:在类加载时已经创建好对象,等待被程序使用懒汉式:在需要使用对象时才去创建该单例对象 三、饿汉模式 3.1、正常创建

构造器私有(外部无法直接new对象)手动创建好一个类对象提供方法给外部获取该对象

public class Hungry {
    //构造器私有,外部无法直接new对象
    private Hungry() {}
    
    //手动创建静态单实例
    private final static Hungry HUNGRY = new Hungry();

    //给外部提供获取该单实例的方法
    public static Hungry getInstance() {
        return 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];
3.2、静态内部类
public class Holder {

    private Holder() {
    }

    private static Holder getInstance() {
        return InnerClass.HOLDER;
    }

    public static class InnerClass {
        private static final Holder HOLDER = new Holder();
    }

}


四、懒汉模式 4.1、单线程使用

存在线程不安全的问题

public class Lazy {

    private Lazy() {}

    private static Lazy lazy;

    public static Lazy getInstance() {
        if (lazy == null) {
            lazy = new Lazy();
        }
        return lazy;
    }
    
}
4.2、双重检测锁(DCL)+原子性操作

new对象不是原子性操作,内部执行以下

    分配内存空间执行构造方法,初始化对象把对象指向内存空间

但jvm有可能出现指令重排(执行132)

A线程执行了13,这个时候lazy的内存空间不等于null,但其实对象还未初始化B线程进来了,虽然B进不去同步代码块,但同步代码块前的空判断会判断为非空此时lazy未完成构造,会直接return空对象

可以使用volatile关键字防止指令重排的发生

public class Lazy {

    private Lazy() {}

    //防止初始化过程指令重排
    private volatile static Lazy lazy;

    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }

}
五、反射与单例的斗争 5.1、反射破坏单例
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    Lazy instance1 = Lazy.getInstance();
    Constructor declaredConstructor = Lazy.class.getDeclaredConstructor(null);
    //无视私有构造
    declaredConstructor.setAccessible(true);
    Lazy instance2 = declaredConstructor.newInstance();
    System.out.println(instance1);
    System.out.println(instance2);
}

5.2、私有构造加锁–三重检测锁

但这种只能存在先获取了单例,再通过反射创建对象,如果对象都是反射创建的,这样也无法避免

private Lazy() {
    synchronized (Lazy.class) {
        if (lazy != null) {
            throw new RuntimeException("请勿使用反射破坏单例");
        }
    }
}

5.3、反射创建对象
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    //Lazy instance1 = Lazy.getInstance();
    Constructor declaredConstructor = Lazy.class.getDeclaredConstructor(null);
    //无视私有构造
    declaredConstructor.setAccessible(true);
    Lazy instance2 = declaredConstructor.newInstance();
    Lazy instance3 = declaredConstructor.newInstance();
    System.out.println(instance2);
    System.out.println(instance3);
}

5.4、私有构造加标记判断
private static boolean laptoy = false;
private Lazy() {
    synchronized (Lazy.class) {
        if (laptoy == false) {
            laptoy = true;
        } else {
            throw new RuntimeException("请勿使用反射破坏单例");
        }
    }
}

5.5、若标记被找到
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
    //Lazy instance1 = Lazy.getInstance();
    Field laptoy = Lazy.class.getDeclaredField("laptoy");
    laptoy.setAccessible(true);
    Constructor declaredConstructor = Lazy.class.getDeclaredConstructor(null);
    //无视私有构造
    declaredConstructor.setAccessible(true);
    Lazy instance2 = declaredConstructor.newInstance();
    
	laptoy.set(instance2, false);
	
    Lazy instance3 = declaredConstructor.newInstance();
    System.out.println(instance2);
    System.out.println(instance3);
}

六、枚举(无法被反射破坏) 6.1、使用
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;  //相同
    }
}
6.2、验证

1、从生成的target目录发现EnumSingle源码类有私有构造

2、尝试用反射newInstance

class Test {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

3、报错,没有此类型的构造器,也就是没有私有无参构造器

4、达不到预期,应该是抛出无法反射地创建枚举对象

5、反编译该类,的确有该构造,但是反射是绝对正确的

6、使用更专业的反编译工具,发现该类没有无参构造,但有一个有参构造

class Test {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

达到预期效果,反射无法破坏单例

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/754261.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号