设计模式---单例模式

定义:
想确保任何情况下都绝对只有一个实例

优点:
1、在内存里只有一个实例,减少了内存开销
2、可以避免对资源的多重占用
3、设置全局访问点,严格控制访问

缺点:没有接口,扩展困难

重点:
1、私有构造器
2、线程安全
3、延迟加载
4、序列化和反序列化安全
5、防止反射攻击

实现方式:
1、Double Check(双重检查锁)
2、静态内部类

Coding

Double Check模式:

public class LazyDoubleCheckSingleton {

    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;

    private LazyDoubleCheckSingleton() {
    }

    public static LazyDoubleCheckSingleton getInstance() {
        if (lazyDoubleCheckSingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                    //1、分配内存给这个对象
                    //2、初始化对象
                    //3、设置lazyDoubleCheckSingleton 指向刚分配的内存地址

                    //如果存在指令重排序的话 即 1、3、2的话 会存在问题。
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

volatile的作用就是防止指令重排序(如下图是重排序后的问题):
在这里插入图片描述
静态内部类:

public class StaticInnerClassSingleton {

    private StaticInnerClassSingleton() {
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }

    private static class InnerClass{

        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();

    }

}

在这里插入图片描述
饿汉式

import java.io.Serializable;

public class HungrySingleton implements Serializable {

    private final static HungrySingleton hungrySingleton;

    static {
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}

单例模式的破坏:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HungrySingleton instance = HungrySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        HungrySingleton newInstance = (HungrySingleton) ois.readObject();
        System.out.println(instance == newInstance);
    }

}
false

进行修改:

import java.io.Serializable;

public class HungrySingleton implements Serializable {

    private final static HungrySingleton hungrySingleton;

    static {
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

	//新增方法
    public Object readResolve() {
        return hungrySingleton;
    }
}

再次执行:

true

解析:

Test.java	HungrySingleton newInstance = (HungrySingleton) ois.readObject();
java.io.ObjectInputStream#readObject	Object obj = readObject0(false);
java.io.ObjectInputStream#readObject0	case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared));
java.io.ObjectInputStream#readOrdinaryObject	obj = desc.isInstantiable() ? desc.newInstance() : null;//创建了新的实例
java.io.ObjectInputStream#readOrdinaryObject	desc.hasReadResolveMethod()) //判断是否存在readResolve方法,(注释解读)
java.io.ObjectInputStream#readOrdinaryObject	Object rep = desc.invokeReadResolve(obj); //调用readResolve方法

防止单例模式反射攻击:

	//反射攻击
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<HungrySingleton> declaredConstructor = HungrySingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton newInstance = declaredConstructor.newInstance();
        System.out.println(instance == newInstance);
    }
false

解决:


import java.io.Serializable;

public class HungrySingleton implements Serializable {

    private final static HungrySingleton hungrySingleton;

    static {
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton() {
        //防止反射调用
        if (hungrySingleton != null) {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

    //防止反序列化
    public Object readResolve() {
        return hungrySingleton;
    }
}

注:双重检查锁 模式防止单例破坏 是无法做到的,我们可以通过定义flag==true,然后在构造方法中 判断flag是否为true,为true则设置为false,否则抛出异常, 但是反射还是可以动态修改flag的值,所以还是无法阻止单例破坏。

枚举解决反射破坏单例:

public enum  EnumInstance {

    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumInstance getInstance() {
        return INSTANCE;
    }
}

枚举序列化校验:

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        EnumInstance instance = EnumInstance.getInstance();
        instance.setData(new Object());
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        EnumInstance newInstance = (EnumInstance) ois.readObject();
        System.out.println(instance == newInstance);
        System.out.println(instance.getData() == newInstance.getData());
    }
true
true

枚举反射校验

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumInstance> declaredConstructor = EnumInstance.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        EnumInstance instance = EnumInstance.getInstance();
        EnumInstance newInstance = declaredConstructor.newInstance();
        System.out.println(instance == newInstance);
        System.out.println(instance.getData() == newInstance.getData());
    }
//没有找到构造方法
Exception in thread "main" java.lang.NoSuchMethodException: com.jiangzheng.course.dubbo.provider.pattern.creational.singleton.EnumInstance.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.jiangzheng.course.dubbo.provider.pattern.creational.singleton.Test.main(Test.java:47)

我们在java.lang.Enum中找到了构造方法:

    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

我们重试修改测试方法:

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumInstance> declaredConstructor = EnumInstance.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumInstance instance = EnumInstance.getInstance();
        EnumInstance newInstance = declaredConstructor.newInstance("test", 666);
        System.out.println(instance == newInstance);
        System.out.println(instance.getData() == newInstance.getData());
    }
//提示 枚举类型不能够被反射调用
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.jiangzheng.course.dubbo.provider.pattern.creational.singleton.Test.main(Test.java:50)

彩蛋:

jad的使用:
登录网站下载工具:https://varaneckas.com/jad/

枚举中声明方法

public enum  EnumInstance {

    INSTANCE{
        protected void printTest(){
            System.out.println("print text");
        }
    };

    protected abstract void printTest();

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumInstance getInstance() {
        return INSTANCE;
    }
}
    public static void main(String[] args) {
        EnumInstance.getInstance().printTest();
    }

基于容器的单例模式:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {

    private static Map<String,Object> singletonMap = new ConcurrentHashMap<>();

    public static void putInstance(String key, Object instance) {
        if (StringUtils.isNoneEmpty(key) && instance != null) {
            if (singletonMap.containsKey(key)) {
                singletonMap.put(key, instance);
            }
        }
    }

    public static Object getInstance(String key) {
        return singletonMap.get(key);
    }

}

基于ThreadLocal的单例模式

//每个线程内部获取时 都是单例的
public class ThreadLocalInstance {
    
    private static final ThreadLocal<ThreadLocalInstance> threadLocal = new ThreadLocal<ThreadLocalInstance>(){
        @Override
        protected ThreadLocalInstance initialValue() {
            return new ThreadLocalInstance();
        }
    };

    private ThreadLocalInstance() {
    }

    public static ThreadLocalInstance getInstance() {
        return threadLocal.get();
    }
}

JDK中Runtime中有使用饿汉式单例模式。
Mybatis中的ErrorContext.java 采用的是ThreadLocal的模式单例

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值