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

设计模式——单例模式

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

设计模式——单例模式

目录

一、单例模式

1. 饿汉式

2. 懒汉式

3. 静态内部类实现

总结:

4. 反射:

但是极限情况下有可能标志位被破解,找到标志位之后也进行反射,修改权限

​编辑

5. 枚举类型:


一、单例模式

1. 饿汉式

每个类只初始化一个对象。这个对象,无论你需要不需要,我都会给你,因此有可能会造成内存空间浪费。也因此引出懒汉式单例模式

package singleton;

public class Hungry {
    //饿汉式单例模式
    //可能会浪费空间,我们想要在使用的时候再申请内存空间,因此引入懒汉式
    private byte[] data1 = new byte[1024*1024];
    Private byte[] data2 = new byte[1024*1024];

    private Hungry(){  //构造方法私有化
    }
    private final static Hungry Hungry = new Hungry();
    public static Hungry getInstance(){
        return Hungry;
    }
}

2. 懒汉式

每个类只初始化一个对象,起初这个对象只是声明,在你需要的时候才会:

1. 分配内存空间

2. 执行构造方法,初始化对象

3. 对象指向内存空间

遇到的问题:

多线程下并发问题:双重锁机制

原子性问题: 添加volatile关键字防止指令重排

package singleton;

public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "   oK!");
    }
    //DCL + volatile——》 双重检测锁 + 原子性操作
    //双重检测 锁 模式的懒汉式单例——》DCL
    //DCL也会出现问题,因为LazyMan = new LazyMna();的时候并不是原子操作,会出现指令重排的现象
    //因此需要进一步改进,添加volatile关键字保证原子性操作

    public static volatile LazyMan LazyMan;
    public static LazyMan getInstance(){
        if(LazyMan == null){
            synchronized (LazyMan.class) {
                if (LazyMan == null) {
                    LazyMan = new LazyMan();  //不是原子性操作
                    //1. 分配内存空间
                    //2. 执行构造方法,初始化对象
                    //3. 将对象指向空间
                    //我们期望123,但多线程下可能会遇到:
                    //线程A: 132;
                    //线程B看到对象指向的空间不为null,因此就会直接return LazyMan对象,
                    //但是此时还没有完成构造方法,造成指向的对象时一块虚无的地址
                }
            }
        }

        return LazyMan;
    }
    //多线程并发条件下会出现问题
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

3. 静态内部类实现

通过静态内部类调用单例模式

package singleton;

public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return innerClass.Holder;
    }
    //静态内部类的实现
    public static class innerClass{
        private static final Holder Holder = new Holder();
    }
    public static void main(String[] args) {
    }
}

总结:

会遇到反射,以上三种方法都是不安全的。


4. 反射:

反射走的是无参构造器,我们可以在无参构造器里面添加判断,来防止反射破坏单例模式

情况一:构造方法构造一个实例之后再使用反射构造一个实例,利用构造器里面添加第三重检测和锁来防止反射破坏单例模式

package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;


public class LazyMan {

    private LazyMan(){
        synchronized (LazyMan.class) {
            if(LazyMan != null) {  //第三重检测,以及加锁
                throw new RuntimeException("不要试图通过反射破坏单例模式!");
            }
        }
    }
    //DCL + volatile——》 双重检测锁 + 原子性操作
    //双重检测 锁 模式的懒汉式单例——》DCL
    //DCL也会出现问题,因为LazyMan = new LazyMna();的时候并不是原子操作,会出现指令重排的现象
    //因此需要进一步改进,添加volatile关键字保证原子性操作

    public static volatile LazyMan LazyMan;

    public static LazyMan getInstance(){
        if(LazyMan == null){
            synchronized (LazyMan.class) {
                if (LazyMan == null) {
                    LazyMan = new LazyMan();  //不是原子性操作
                    //1. 分配内存空间
                    //2. 执行构造方法,初始化对象
                    //3. 将对象指向空间
                    //我们期望123,但多线程下可能会遇到:
                    //线程A: 132;
                    //线程B看到对象指向的空间不为null,因此就会直接return LazyMan对象,
                    //但是此时还没有完成构造方法,造成指向的对象时一块虚无的地址
                }
            }
        }

        return LazyMan;
    }
    //多线程并发条件下会出现问题
    public static void main(String[] args) throws Exception {
        LazyMan instance1 = LazyMan.getInstance();
        Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}


情况二:两个实例都使用反射构造,利用红绿灯防止破坏。

设置一个标志位flag经过加密,这样可以防止单例模式被破坏。

package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;

public class LazyMan {
    private static boolean flag = false;
    private LazyMan(){
        synchronized (LazyMan.class) {
            if(flag == false) {
                flag = true;
            }else{
                throw new RuntimeException("不要试图通过反射破坏单例模式!");
            }
        }
    }

    public static volatile LazyMan LazyMan;
    public static LazyMan getInstance(){
        if(LazyMan == null){
            synchronized (LazyMan.class) {
                if (LazyMan == null) {
                    LazyMan = new LazyMan();
                }
            }
        }

        return LazyMan;
    }
    //多线程并发条件下会出现问题
    public static void main(String[] args) throws Exception {
        Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance1 = declaredConstructor.newInstance();
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

但是极限情况下有可能标志位被破解,找到标志位之后也进行反射,修改权限
package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;


public class LazyMan {
    private static boolean flag = false;
    private LazyMan(){
        synchronized (LazyMan.class) {
            if(flag == false) {
                flag = true;
            }else{
                throw new RuntimeException("不要试图通过反射破坏单例模式!");
            }
        }
    }

    public static volatile LazyMan LazyMan;
    public static LazyMan getInstance(){
        if(LazyMan == null){
            synchronized (LazyMan.class) {
                if (LazyMan == null) {
                    LazyMan = new LazyMan();
                }
            }
        }

        return LazyMan;
    }
    //多线程并发条件下会出现问题
    public static void main(String[] args) throws Exception {
        Field flag = LazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);//破坏标志位的私有权限,通过反射构造一个实例之后,再把flag的值改回来,这样下次反射又可以构造实例
        Constructor declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance1 = declaredConstructor.newInstance();
        flag.set(instace1, false);
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}


5. 枚举类型:

可以防止反射破坏单例模式,会抛出反射异常

枚举没有无参构造,只有有参构造,并且有两个参数(String,int)

package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;


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

 

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

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

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