12

单例模式的实现方式及如何有效防止防止反射和反序列化

 3 years ago
source link: https://www.cnblogs.com/call-me-pengye/p/11169051.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

单例模式的实现方式及如何有效防止防止反射和反序列化

方式一:饿汉式(静态常量)

public class Singleton {
    
    private final static Singleton SINGLETON = new Singleton();
    
    private Singleton(){
    }
    
    public void doAction(){
        //TODO 实现你需要做的事
    }
    
    public static Singleton getInstance(){
        return SINGLETON;
    }
}

测试用例:

public class Test {
    public static void main(String[] args) {
        Signleton singleton1 = Singleton.getInstance();
        Signleton singleton2 = Singleton.getInstance();
        
        System.out.println("两个singleton对象是否是同一个对象:"+ (singleton1 == singleton2) );
        System.out.println("singleton1的hashCode:"+singleton1.hashCode());
        System.out.println("singleton2的hashCode:"+singleton2.hashCode());
    }
}

运行结果:

两个singleton对象是否是同一个对象:true
singleton1的hashCode:366712642
singleton2的hashCode:366712642
  1. 代码实现简单
  2. 利用类加载机制避免了多线程同步问题
  1. 在类加载时就完成了实例化,没有达到Lazy loading的效果,有可能造成内存浪费

方式二:饿汉式(静态代码块)

public class Singleton {
    
    private final static Singleton SINGLETON;
    static{
        SINGLETON = new Singleton();
    }
    
    private Singleton(){
    }
    public void doAction(){
        //TODO 实现你需要做的事
    }
    
    public static Singleton getInstance(){
        return SINGLETON;
    }
}

测试用例:

public class Test {
    public static void main(String[] args) {
        Signleton singleton1 = Singleton.getInstance();
        Signleton singleton2 = Singleton.getInstance();
        
        System.out.println("两个singleton对象是否是同一个对象:"+ (singleton1 == singleton2) );
        System.out.println("singleton1的hashCode:"+singleton1.hashCode());
        System.out.println("singleton2的hashCode:"+singleton2.hashCode());
    }
}

运行结果:

两个singleton对象是否是同一个对象:true
singleton1的hashCode:366712642
singleton2的hashCode:366712642

这种实现方式优缺点和方式一是一样的,也是利用了类加载,唯一不同的就是将实例化的过程放在了静态代码块中。


方式三:懒汉式(线程不安全)

public class Singleton {

    private static Singleton singleton;
    private Singleton(){
    }
    public void doAction(){
        //TODO 实现你需要做的事
    }
    
    public static Singleton getInstance(){
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

测试代码:

public class Test {
    public static void main(String[] args) {
        //多线程获取对象,存在线程不安全问题
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton1 = Singleton.getInstance();
                System.out.println("singleton1的hashCode:"+singleton1.hashCode());
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton2 = Singleton.getInstance();
                System.out.println("singleton2的hashCode:"+singleton2.hashCode());
            }
        });
        thread1.start();
        thread2.start();
    }
}

运行结果:

第一次运行结果:
    singleton2的hashCode:1813990537
    singleton1的hashCode:1813990537
第二次运行结果:
    singleton1的hashCode:1813990537
    singleton2的hashCode:1481479505

从两次运行结果来看,我们发现singleton1与singleton2的hashCode存在相同和不想同的两种情况,这就已经证明了这种方式的线程不安全性

优点:

  1. 起到了Lazy loading效果,适合在单线程环境中使用

缺点:

  1. 在多线程环境中存在线程不安全问题

 方式四:懒汉式(方法同步)

public class Singleton {
    private static Singleton singleton;

    private Singleton(){
    }
    public void doAction(){
        //TODO 实现你需要做的事
    }
    public synchronized static Singleton getInstance(){
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

测试用例(可多做几次测试):

public class Test {
    public static void main(String[] args) {
        //多线程获取对象,线程安全问题
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton1 = Singleton.getInstance();
                System.out.println("singleton1的hashCode:"+singleton1.hashCode());
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton2 = Singleton.getInstance();
                System.out.println("singleton2的hashCode:"+singleton2.hashCode());
            }
        });
        thread1.start();
        thread2.start();
    }
}

运行结果:

第一次运行结果:
    singleton1的hashCode:1947425526
    singleton2的hashCode:1947425526
第二次运行结果:
    singleton1的hashCode:1430007319
    singleton2的hashCode:1430007319

从两次运行结果来看,我们发现singleton1与singleton2的hashCode是一样的,说明这种方法是线程安全的

优点:

  1. 实现了Lazy loading想过
  2. 避免了多线程同步问题

缺点:

  1. 效率太低,每个线程在执行getInstance()方法都要进行同步。实际上这个方法只要执行一次实例化就行,当实例化完成,后面的线程是通不过if判断的

方式五:懒汉式(实例化代码同步)

public class Singleton {
    private static Singleton singleton;
    
    public Singleton() {
    }
    public void doAction(){
        //TODO 实现你需要做的事
    }
    public  static Singleton getInstance(){
        if (singleton == null) {
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

测试用例:

public class Test {
    public static void main(String[] args) {
        //多线程获取对象,存在线程不安全问题
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton1 = Singleton.getInstance();
                System.out.println("singleton1的hashCode:"+singleton1.hashCode());
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton2 = Singleton.getInstance();
                System.out.println("singleton2的hashCode:"+singleton2.hashCode());
            }
        });
        thread1.start();
        thread2.start();
    }
}

运行结果:

singleton2的hashCode:1813990537
singleton1的hashCode:1430007319

从两次运行结果来看,我们发现singleton1与singleton2的hashCode是不相同的,证明这种方式是线程不安全的

有点:

  1. 实现了Lazy loading的现过
  2. 相对于第四种的同步,该方法的效率得到了提升

 缺点:

  1. 在多线程环境中存在线程不安全问题

方式六:双重检测

public class Singleton {
    private static Singleton singleton;
    
    private Singleton(){
    }
    public void doAction(){
        //TODO 实现你需要做的事
    }
    public  static Singleton getInstance(){
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

测试用例:

public class Test {
    public static void main(String[] args) {
        //多线程获取对象,线程安全
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton1 = Singleton.getInstance();
                System.out.println("singleton1的hashCode:"+singleton1.hashCode());
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton2 = Singleton.getInstance();
                System.out.println("singleton2的hashCode:"+singleton2.hashCode());
            }
        });
        thread1.start();
        thread2.start();
    }
}

运行结果:

singleton2的hashCode:1481479505
singleton1的hashCode:1481479505
  1. 实现Lazy loading

方式七:静态内部类

public class Singleton {
    public Singleton() {
    }
    public void doAction(){
        //TODO 实现你需要做的事
    }
    private static class SingletonInstance{
        private final static Singleton SINGLETON = new Singleton();
    }
    public  static Singleton getInstance(){
        return SingletonInstance.SINGLETON;
    }
}

测试用例:

public class Test {
    public static void main(String[] args) {
        //多线程获取对象,线程安全
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton1 = Singleton.getInstance();
                System.out.println("singleton1的hashCode:"+singleton1.hashCode());
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton2 = Singleton.getInstance();
                System.out.println("singleton2的hashCode:"+singleton2.hashCode());
            }
        });
        thread1.start();
        thread2.start();
    }
}

运行结果:

singleton2的hashCode:1481479505
singleton1的hashCode:1481479505

这种方式利用了类装载机制来保证初始化实例时只有一个线程,静态内部类在Singleton被装载时并不会立即实例化,而是在调用getInstance()时才会装载静态内部类,从而完成Singleton实例化。由于类的静态属性只会在第一次加载类的时候进行初始化,这里我们通过JVM加载类时的线程安全的特性来保证了线程安全

优点:

  1. 利用JVM加载静态内部类的机制保证多线程安全
  2. 实现Lazy loading效果
  3. 效率高

方式八:使用枚举(《Effective Java》作者的Josh Bloch提倡的方式)

public enum Singleton {
    INSTANCE;
    public void doAction(){
        //TODO 实现你需要做的事
    }
}

测试用例:

public class Test {
    public static void main(String[] args) {
        //多线程获取对象,线程安全
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton1 = Singleton.INSTANCE;
                System.out.println("singleton1的hashCode:"+singleton1.hashCode());
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                Singleton singleton2 = Singleton.INSTANCE;
                System.out.println("singleton2的hashCode:"+singleton2.hashCode());
            }
        });
        thread1.start();
        thread2.start();
    }
}

运行结果:

singleton2的hashCode:321306952
singleton1的hashCode:321306952

优点:

  1. 线程安全(枚举实例的创建默认就是线程安全的)
  2. 不会因为序列化而产生新实例
  3. 防止反射攻击

破环单例模式的三种方式:反射,序列化,克隆

以双重检测方式为例测试反射,序列化,克隆是否能破环单例模式:

public class Singleton  implements Serializable,Cloneable{
    private static final long serialVersionUID = 6125990676610180062L;
    private static Singleton singleton;
    
    private Singleton(){
    }
    public void doAction(){
        //TODO 实现你需要做的事
    }
    public  static Singleton getInstance(){
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

测试用例:

public class DestroySingleton {
    
    public static void main(String[] args) throws Exception {
        //通过getInstance()获取
        Singleton singleton = Singleton.getInstance();
        System.out.println("singleton的hashCode:"+singleton.hashCode());
        //通过反射获取
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton reflex = constructor.newInstance();
        System.out.println("reflex的hashCode:"+reflex.hashCode());
        //通过克隆获取
        Singleton clob = (Singleton) Singleton.getInstance().clone();
        System.out.println("clob的hashCode:"+clob.hashCode());
        //通过序列化,反序列化获取
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(Singleton.getInstance());
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        Singleton serialize = (Singleton) ois.readObject();
        if (ois != null)    ois.close();
        if (bis != null) bis.close();
        if (oos != null) oos.close();
        if (bos != null) bos.close();
        System.out.println("serialize的hashCode:"+serialize.hashCode());
    }
}

运行结果:

singleton的hashCode:366712642
reflex的hashCode:1829164700
clob的hashCode:2018699554
serialize的hashCode:990368553

运行结果表明通过getInstance()、反射、克隆、序列化这四种方式得到的Singleton对象的hashCode是不一样的,此时单例模式已然被破环


如何防止反射、克隆、序列化对单例模式的破环

1、防止反射破环(虽然构造方法已私有化,但通过反射机制使用newInstance()方法构造方法也是可以被调用):

  • 首先定义一个全局变量开关isFristCreate默认为开启状态
  • 当第一次加载时将其状态更改为关闭状态

2、防止克隆破环

  • 重写clone(),直接返回单例对象

3、防止序列化破环

  • 添加readResolve(),返回Object对象
public class Singleton  implements Serializable,Cloneable{
    private static final long serialVersionUID = 6125990676610180062L;
    private static Singleton singleton;
    private static boolean isFristCreate = true;//默认是第一次创建
    
    private Singleton(){
            if (isFristCreate) {
                synchronized (Singleton.class) {
            if (isFristCreate) {
              isFristCreate = false;
            } } }else{ throw new RuntimeException("已然被实例化一次,不能在实例化"); } } public void doAction(){ //TODO 实现你需要做的事 } public static Singleton getInstance(){ if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } @Override protected Singleton clone() throws CloneNotSupportedException { return singleton; } private Object readResolve() { return singleton; } }

测试用例:

public class DestroySingleton {
    
    public static void main(String[] args) throws Exception {
        //通过getInstance()获取
        Singleton singleton = Singleton.getInstance();
        System.out.println("singleton的hashCode:"+singleton.hashCode());
        //通过克隆获取
        Singleton clob = (Singleton) Singleton.getInstance().clone();
        System.out.println("clob的hashCode:"+clob.hashCode());
        //通过序列化,反序列化获取
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(Singleton.getInstance());
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        Singleton serialize = (Singleton) ois.readObject();
        if (ois != null)    ois.close();
        if (bis != null) bis.close();
        if (oos != null) oos.close();
        if (bos != null) bos.close();
        System.out.println("serialize的hashCode:"+serialize.hashCode());
        //通过反射获取
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton reflex = constructor.newInstance();
        System.out.println("reflex的hashCode:"+reflex.hashCode());
    }
}

运行结果:

singleton的hashCode:366712642
clob的hashCode:366712642
serialize的hashCode:366712642
Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at designPatterns.singleton.doublecheck.DestroySingleton.main(DestroySingleton.java:33)
Caused by: java.lang.RuntimeException: 已然被实例化一次,不能在实例化
    at designPatterns.singleton.doublecheck.Singleton.<init>(Singleton.java:16)
    ... 5 more

从运行结果上看重写clone(),添加readResolve()后通过克隆和序列化得到的对象的hashCode与从getInstance()得到的对象得而hashCode值相同,而通过反射运行得到的结果符合预想的报错;因为以上三种手段对防止单例被破坏起作用了,至于枚举为什么能做到防止反射,克隆及序列化对单例的破坏将留在下次分享

  如有写的不对的地方请书友们及时指出,谨诚拜谢!!


Recommend

  • 77
    • blog.51cto.com 7 years ago
    • Cache

    单例的实现方式-姑苏城

    单例模式的实现方式:单例模式要求程序中类只有一个对象。所以我们要将他的构造函数设为private,并提供一个生成对象的静态方法。第一种实现方式:&nbsp;&nbsp;由于第一种方式是线程不安全的。因为当在if(instace==null)这句话中出现了中断,期间,又有另一...

  • 45

    在Python中如何实现单例模式?这可以说是一个经典的Python面试题了。这回我们讲讲实现Python中实现单例模式的n种方式,和它的原理。 什么是单例模式

  • 37

    一 单例模式概述 (一) 什么是单例模式 单例模式属于 创建型模式 之一,它提供了一种创建对象的...

  • 11
    • blog.csdn.net 4 years ago
    • Cache

    单例模式之枚举实现

    单例模式之枚举实现_刘伟技术博客-CSDN博客        如果你没有学过单例模式,请点击:确保对象的唯一性——单例模式。         有很多网友留言说我漏掉了一种非...

  • 9
    • www.cnblogs.com 4 years ago
    • Cache

    单例模式6种实现及利弊分析

    目录 单例模式6种实现及利弊分析 2.懒汉式(非线程安全) 3.懒汉式(synchronized) 4.懒汉式(静态内部类) 5.懒汉式(双重锁DCL)...

  • 16
    • 微信 mp.weixin.qq.com 4 years ago
    • Cache

    C#实现单例模式的几种方法

    介绍 非线程安全的单例版本 简单线程安全版本(通过lock实现) Double-checked locking 不完全懒汉式,但不加锁的线程安...

  • 9

    您现在的位置:首页 --> 其他 --> Python创建单例模式的三种方式 Python创建单例模式的三种方式

  • 12

    枚举防止反射,克隆及序列化破环单例模式的原理   在上一篇文章中详细的介绍了实现单例模式的几种方式,以及...

  • 8

    五种方式实现 Java 单例模式 单例模式(Singleton...

  • 7

    单例模式五种实现方式以及在JDK中的体现 一、五种实现方式 1、饿汉式 提供一个静态私有的成员常量,类型就是单例类型,值是用私有构造创造出来...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK