单例模式

来源:互联网 发布:阿里云学生认证条件 编辑:程序博客网 时间:2024/05/18 13:27

参考原文博客:点击进入

在此谢过原博主。

单例模式有5种形式;分别是饿汉模式懒汉模式双重检测锁模式静态内部类模式枚举模式。其中双重检测锁本人不清楚这么做的原因,并且原博主建议在JDK修复同步块嵌套漏洞之前不使用。静态内部类模式咋一看还不如写回饿汉模式,但是静态内部类模式实现了延迟加载,提供了系统性能。
以下是代码:
(1)饿汉模式

package cn.test.Singleton.one;/** * 饿汉式 * 未实现延时加载,线程安全?为什么说是线程安全, * 因为在类加载的时候就已经实例化了instance,所以当多个线程并发 * 访问getInstance()的时候都是得到同一个实例instance,不会因为 * 并发的问题出现实例化两个对象 * @author cjc * */public class SingleTon_EH {    private static SingleTon_EH instance = new SingleTon_EH();    private SingleTon_EH(){}    public static SingleTon_EH getInstance(){        return instance;    }}

(2)懒汉模式

package cn.test.Singleton.two;/** * 懒汉式 * 实现了延迟加载 * 线程不安全。为什么线程不安全? * 因为在多个线程并发访问getInstance()的时候,可能因为 * 并发的问题同时执行if(null == instance){instance = new SingleTon_LH();} * 这段代码,导致创建了不止一个实例。所以可以在getInstance()加synchronized锁,但是牺牲 * 了高并发性能 *  * 总的来说: * 实现延迟加载 * 线程安全但是牺牲了高并发性能 *  * @author cjc * */public class SingleTon_LH {    private static SingleTon_LH instance = null;    private SingleTon_LH(){}    public static synchronized SingleTon_LH getInstance(){        if(null == instance){            instance = new SingleTon_LH();        }        return instance;    }}

(3)双重检测锁模式

package cn.test.Singleton.three;/** * 双重检测锁式单例模式 * 实现延迟加载 * 线程安全 *  * 在jdk修复同步块嵌套之前不推荐,这里只作了解 * @author cjc * */public class SingleTon_DoubleLock {    private static SingleTon_DoubleLock instance = null;    private SingleTon_DoubleLock(){}    public static SingleTon_DoubleLock getInstance(){        if(null == instance){            SingleTon_DoubleLock st;            synchronized (SingleTon_DoubleLock.class) {                st = instance;                if(null == st){                    synchronized (SingleTon_DoubleLock.class) {                        if(null == st){                            st = new SingleTon_DoubleLock();                        }                        instance = st;                    }                }            }        }        return instance;    }}

(4)静态内部类模式

package cn.test.Singleton.four;/** * 静态内部类式单例模式 * 实现延迟加载。 * 线程安全 * @author cjc * */public class SingleTon_staticInner {    private SingleTon_staticInner(){}    private static class Inner{        public static final SingleTon_staticInner instance = new SingleTon_staticInner();     }    public static SingleTon_staticInner getInstance(){        return Inner.instance;    }}

(5)枚举类模式

package cn.test.Singleton.five;/** * 枚举式单例。 * 未延迟加载 * 线程安全 * 原生防止发射与序列化击穿 * @author cjc * */public enum SingleTon_Enum {    INSTANCE;}

测试:

package cn.test.Singleton.instance;import org.junit.Test;import cn.test.Singleton.five.SingleTon_Enum;import cn.test.Singleton.four.SingleTon_staticInner;import cn.test.Singleton.one.SingleTon_EH;import cn.test.Singleton.three.SingleTon_DoubleLock;import cn.test.Singleton.two.SingleTon_LH;public class Test1 {    @Test    public void test01(){        SingleTon_EH s11 = SingleTon_EH.getInstance();        SingleTon_EH s12 = SingleTon_EH.getInstance();        System.out.println(s11 == s12);        SingleTon_LH s21 = SingleTon_LH.getInstance();        SingleTon_LH s22 = SingleTon_LH.getInstance();        System.out.println(s21 == s22);        SingleTon_DoubleLock s31 = SingleTon_DoubleLock.getInstance();        SingleTon_DoubleLock s32 = SingleTon_DoubleLock.getInstance();        System.out.println(s31 == s32);        SingleTon_staticInner s41 = SingleTon_staticInner.getInstance();        SingleTon_staticInner s42 = SingleTon_staticInner.getInstance();        System.out.println(s41 == s42);        SingleTon_Enum s51 = SingleTon_Enum.INSTANCE;        SingleTon_Enum s52 = SingleTon_Enum.INSTANCE;        System.out.println(s51.getClass()+","+s52.getClass());        System.out.println(s51 == s52);    }}

输出:

truetruetruetrueclass cn.test.Singleton.five.SingleTon_Enum,class cn.test.Singleton.five.SingleTon_Enumtrue

总结:以上每一个都是线程安全的,其中懒汉模式是通过synchronize保证了线程安全但牺牲了高并发性能。

以上的几种模式都有漏洞,可以用序列化的方式或者是用反射机制去破解,得到两个不同的实例,下面我们看看如何避免这种情况的发生:
(1)序列化
例子:饿汉模式:
代码是:

public class SingleTon_EH implements Serializable{    private static final long serialVersionUID = 1L;    private static SingleTon_EH instance = new SingleTon_EH();    private SingleTon_EH(){}    public static SingleTon_EH getInstance(){        return instance;    }}   

序列化代码:

    /**     * 测试序列化,饿汉模式     */    @Test    public void test03(){        SingleTon_EH instance = SingleTon_EH.getInstance();        ObjectOutputStream out = null;        ObjectInputStream in = null;        try{            out = new ObjectOutputStream(                    new FileOutputStream("temp.txt"));            out.writeObject(instance);            //反序列化            in = new ObjectInputStream(                    new FileInputStream("temp.txt"));            SingleTon_EH insIn = (SingleTon_EH)in.readObject();            System.out.println(instance);            System.out.println(insIn);        }catch (Exception e) {        }finally{        }    }

那么得到的结果是:

cn.test.Singleton.one.SingleTon_EH@b23b25ccn.test.Singleton.one.SingleTon_EH@575fadcf

在这里,必须提醒一下,当实例化一个对象,再把这个对象序列化到磁盘,以后每次反序列化得到的对象都是不同的,也就是把in流close关了,再打开,再反序列化,得到的对象和之前反序列化的对象是不同的。

那如何去避免了。我们用序列化知识的一个方法readResolve()方法,readResolve()方法有什么作用呢?readResolve()是序列化机制的一个特殊的方法,它可以实现保护性复制整个对象。这个方法会紧接着readObject()或defaultReadObject()被调用,替换读取到的序列化对象,并且将它返回,修改饿汉模式代码如下:

public class SingleTon_EH implements Serializable{    private static final long serialVersionUID = 1L;    private static SingleTon_EH instance = new SingleTon_EH();    private SingleTon_EH(){}    public static SingleTon_EH getInstance(){        return instance;    }    /**     * 如何避免反序列化得到不同的实例呢?     * 反序列化时,如果定义了readResolve(),则直接次方法指定的对象     */    public Object readResolve(){        return instance;    }   }

再次执行test03()方法得到

cn.test.Singleton.one.SingleTon_EH@b23b25ccn.test.Singleton.one.SingleTon_EH@b23b25c

同样的道理,将其他模式都一样的加上以上代码就可以防止序列化了。

(2)反射
反射机制很强大,可以得到private的构造器,并且将private的构造器设为可访问的,那么就可以通过反射得到的构造器new出一个实例出来了,这样又破坏了单例模式只有唯一的实例的情况。
例子:懒汉模式

public class SingleTon_LH {    private static SingleTon_LH instance = null;        public static synchronized SingleTon_LH getInstance(){        if(null == instance){            instance = new SingleTon_LH();        }        return instance;    }    /**     * 如何避免反序列化得到不同的实例呢?     * 反序列化时,如果定义了readResolve(),则直接次方法指定的对象     */    public Object readResolve(){        return instance;    }   }

反射机制代码:

    /**     * 反射破解懒汉模式的单例模式,其他模式同理,但是枚举模式原生避免这种风险     * @throws Exception     */    @Test    public void test02() throws Exception{        //反射加载类        Class<SingleTon_LH> clazz = (Class<SingleTon_LH>) Class.forName("cn.test.Singleton.two.SingleTon_LH");        //反射获得构造器        Constructor<SingleTon_LH> contructor = clazz.getDeclaredConstructor(null);  //无参构造器        contructor.setAccessible(true);        SingleTon_LH s1 = contructor.newInstance(); //无参构造器,参数为空        SingleTon_LH s2 = contructor.newInstance();        SingleTon_LH s3 = SingleTon_LH.getInstance();        System.out.println(s1);        System.out.println(s2);        System.out.println(s3);    }

得到的结果:

cn.test.Singleton.two.SingleTon_LH@5430d082cn.test.Singleton.two.SingleTon_LH@50c931fccn.test.Singleton.two.SingleTon_LH@48f0c0d3

但是当我们加上:

contructor.setAccessible(true);SingleTon_LH ss = SingleTon_LH.getInstance();System.out.println(ss);

这段代码后,程序执行就会保存,报出现运行时错误,这是因为执行了以上代码后instance已经不再是null了。
其他模式的代码一样,只有懒汉模式有一个漏洞,就是当没有执行过getInstance()时,才不能避免反射机制的破坏,修改代码如下:

public class SingleTon_LH {    private static SingleTon_LH instance = null;    private SingleTon_LH(){        if(instance != null){            throw new RuntimeException();        }    }    public static synchronized SingleTon_LH getInstance(){        if(null == instance){            instance = new SingleTon_LH();        }        return instance;    }    /**     * 如何避免反序列化得到不同的实例呢?     * 反序列化时,如果定义了readResolve(),则直接次方法指定的对象     */    public Object readResolve(){        return instance;    }   }
0 0
原创粉丝点击