2_单例设计模式_反序列化破坏单例模式_注册式单例_线程单例实现 ThreadLocal

发布时间:2024年01月10日

三 .反序列化破坏单例模式

个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。

反序列化后的对象会重新分配内存,即重新创建。

public class SeriableSingleton implements Serializable {
    /*
    序列化
    把内存中对象的状态转换为字节码的形式
    把字节码通过IO输出流,写到磁盘上
    永久保存下来,持久化
    -----------------
    反序列化
    将持久化的字节码内容,通过IO输入流读到内存中来
    转化成一个Java对象
    */
    public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(){}
    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }
    //添加这个固定格式的方法(桥接模式)
    private Object readResolve(){ return INSTANCE;}
}


public class SeriableSingletonTest {
    public static void main(String[] args) {
        SeriableSingleton s1 = null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();
        FileOutputStream fos = null;
        try {
            //序列化
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();
            //反序列化
            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SeriableSingleton)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);  //上面SeriableSingleton类,加上readResolve():true; 去掉readResolve():false;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

通过JDK 源码分析我们可以看出,虽然增加 readResolve0方法返回实例解决了单例模式被破坏的问题,

但是实际上实例化了两次,只不过新创建的对象没有被返回而已。如果创建对象的动作发生频率加快,就意味着内存分配开销也会随之增大。

四.注册式单例

注册式单例模式又称为登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。

注册式单例模式有两种:一种为枚举式单例模式,另一种为容器式单例模式。

(1)枚举式单例模式:

public enum EnumSingleton {
    INSTANCE;
    
    //属性get和set赋值
    private Object data;
    public Object getData() {  return data;}
    public void setData(Object data) { this.data = data; }

    //返回单列实例
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

//-------------------------------------

public class EnumSingletonTest {
    public static void main(String[] args) {
        EnumSingleton s1 = null;
        EnumSingleton s2 = EnumSingleton.getInstance();
        s2.setData(new Object());
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();
            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (EnumSingleton)ois.readObject();
            ois.close();
            System.out.println(s1.getData());
            System.out.println(s2.getData());
            System.out.println(s1 == s2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果:
java.lang.Object@b4c966a
java.lang.Object@b4c966a
true
解决: 
反序列化单例模式readResolve()实际上实例化了两次,只不过新创建的对象没有被返回而已。
如果创建对象的动作发生频率加快,就意味着内存分配开销也会随之增大。

 
public class EnumSingletonTest {
    public static void main(String[] args) {
        try {
            Class clazz = EnumSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
            c.setAccessible(true);
            Object o = c.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
结果:[即不能用反射来创建枚举类型]
    java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.gupaoedu.vip.pattern.singleton.test.EnumSingletonTest.main(EnumSingletonTest.java:65)

(2)容器式单例模式:

其实枚举式单例,虽然写法优雅,但是也会有一些问题。因为它在类加载之时就将所有的对象初始化放在类内存中,这其实和饿汉式并无差异,不适合大量创建单例对象的场景。

public class ContainerSingleton {
    private ContainerSingleton(){}

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

    public static Object getInstance(String className){
        Object instance = null;
        if(!ioc.containsKey(className)){
            try {
                instance = Class.forName(className).newInstance();
                ioc.put(className, instance);
            }catch (Exception e){
                e.printStackTrace();
            }
            return instance;
        }else{
            return ioc.get(className);
        }
    }

}

//-------------------------------
public class Pojo {}
public class ContainerSingletonTest {
    public static void main(String[] args) {
        Object instance1 = ContainerSingleton.getInstance("com.gupaoedu.vip.pattern.singleton.test.Pojo");
        Object instance2 = ContainerSingleton.getInstance("com.gupaoedu.vip.pattern.singleton.test.Pojo");
        System.out.println(instance1 == instance2);
    }
}
结果:true

五. 线程单例实现 ThreadLocal

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
            new ThreadLocal<ThreadLocalSingleton>(){
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton(){}

    public static ThreadLocalSingleton getInstance(){
        return threadLocaLInstance.get();
    }
}

//-------------------------------------------------
public class ExectorThread implements Runnable{
    public void run() {
        ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(Thread.currentThread().getName() + ":" + instance);
    }
}
public class ThreadLocalSingletonTest {
    public static void main(String[] args) {
        //main线程
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        //t1和t2线程
        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("End");
    }
}

结果:[在同一个线程下所有的实例都是案例。不同线程间不是单列的]
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6e0be858
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6e0be858
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6e0be858
End
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@50230424
Thread-0:com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@50230424
com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6a3899f6
Thread-1:com.gupaoedu.vip.pattern.singleton.threadlocal.ThreadLocalSingleton@6a3899f6 

特点:

在同一个线程下所有的实例都是案例。不同线程间不是单列的。

六 小结

没有接口,扩展困难
如果要扩展单例对象,只有修改代码,没有其他途径。


私有化构造器
保证线程安全
延迟加载
防止序列化和反序列化破坏单例
防御反射攻击单例

文章来源:https://blog.csdn.net/jinhuding/article/details/135512679
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。