单例模式
来源:互联网 发布:阿里云学生认证条件 编辑:程序博客网 时间: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; } }
- 单例、单例模式
- 单例模式-多线程单例模式
- 单件模式(单例模式)
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- PHP模式-单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- 设计模式--单例模式
- 设计模式-单例模式
- 单例模式(单子模式)
- 设计模式-单例模式
- [设计模式] 单例模式
- LeetCode之求组合数之和二CombinationSumII
- iOS—最全的真机测试教程
- a 与 &a 的区别
- Android 优雅的为RecyclerView添加HeaderView和FooterView
- 深度思考Java成员变量的初始化
- 单例模式
- HTML <!DOCTYPE> 标签
- ListView滑动删除 ,仿腾讯QQ
- 交换两个数的值
- spring 动态数据源不起作用
- Spring JdbcTemplate方法详解
- linux中安装FastDFS-v5.05+nginx-1.8.0
- Wireshark使用总结
- Windows下从零开始学习nodejs4.X+express4.X开发--(2)nodejs交互环境