史上最全的七种单例写法——如何防止单例被破坏
2019-05-31 18:02
1771 查看
什么是单例
单例模式确保某各类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能,每台计算机可以有若干个打印机,但只能有一个Printer spooler,以避免两个打印作业同时输出到打印机中,每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态
单例模式特点
1、单例类只能有一个实例。 2、单例类必须自己创建自己唯一的实例。 3、单例类必须给所有其它对象提供这一实例。
单例模式优缺点
1、单例类只有一个实例 2、共享资源,全局使用 3、节省创建时间,提高性能
单例模式的七种写法
分别是「饿汉」、「懒汉(非线程安全)」、「懒汉(线程安全)」、「双重校验锁」、「静态内部类」、「枚举」和「容器类管理]
1.饿汉式
package com.xuyu.V1; /** * author:须臾 */ public class SingletonV1 { /** * 饿汉式 * 优点:先天线程安全,当类初始化的时候就会创建该对象 * 缺点:如果饿汉式使用频繁,可能会影响项目启动效率 */ private static SingletonV1 singletonV1=new SingletonV1(); /** * 将构造函数私有化,禁止初始化 */ private SingletonV1(){} public static SingletonV1 getInstance(){ return singletonV1; } /** * 测试单例 */ public static void main(String[] args) { SingletonV1 instance1 = SingletonV1.getInstance(); SingletonV1 instance2 = SingletonV1.getInstance(); //结果为true,说明保证了单例 System.out.println(instance1==instance2); } }
源码分析Runtime
//饿汉式单例 public class Runtime { private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } .. }
2.懒汉式(线程不安全)
package com.xuyu.V2; /** * author:须臾 */ public class SingletonV2 { /** * 饿汉式(线程不安全) */ private static SingletonV2 singletonV2; private SingletonV2(){} /** * 创建对象使用 */ public static SingletonV2 getInstance(){ if(singletonV2==null){ try { Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } singletonV2=new SingletonV2(); } return singletonV2; } /** * 测试单例 */ public static void main(String[] args) { for (int i=0;i<100;i++){ new Thread(new Runnable() { public void run() { SingletonV2 instance1 = SingletonV2.getInstance(); System.out.println(Thread.currentThread().getName()+","+instance1); } }).start(); } } }
输出结果:线程不安全
Thread-1,com.xuyu.V2.SingletonV2@383a0ba Thread-4,com.xuyu.V2.SingletonV2@d9d8ad0 Thread-0,com.xuyu.V2.SingletonV2@546431f0 Thread-5,com.xuyu.V2.SingletonV2@2858c11c Thread-22,com.xuyu.V2.SingletonV2@3635f62a Thread-6,com.xuyu.V2.SingletonV2@48369750 Thread-7,com.xuyu.V2.SingletonV2@2770f418 Thread-3,com.xuyu.V2.SingletonV2@6d9da26a Thread-13,com.xuyu.V2.SingletonV2@77355386 Thread-10,com.xuyu.V2.SingletonV2@29580e2d .... Thread-94,com.xuyu.V2.SingletonV2@3945e031 Thread-91,com.xuyu.V2.SingletonV2@5caf9db6
3.懒汉式(线程安全)
package com.xuyu.V3; /** * author:须臾 */ public class SingletonV3 { /** * 懒汉式线程安全 */ private static SingletonV3 singletonV3; private SingletonV3(){} /** * 效率低 */ public synchronized static SingletonV3 getInstance(){ try { Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } if (singletonV3==null){ System.out.println("创建实例SingletonV3"); singletonV3=new SingletonV3(); } System.out.println("获取SingletonV3实例"); return singletonV3; } /** * 测试单例 */ public static void main(String[] args) { for (int i=0;i<100;i++){ new Thread(new Runnable() { public void run() { SingletonV3 instance1 = SingletonV3.getInstance(); System.out.println(Thread.currentThread().getName()+","+instance1); } }).start(); } } }
输出结果
创建实例SingletonV3 获取SingletonV3实例 Thread-0,com.xuyu.V3.SingletonV3@95458f7 获取SingletonV3实例 Thread-99,com.xuyu.V3.SingletonV3@95458f7 获取SingletonV3实例 Thread-98,com.xuyu.V3.SingletonV3@95458f7 获取SingletonV3实例 Thread-97,com.xuyu.V3.SingletonV3@95458f7 获取SingletonV3实例 ....
4.双重检验锁(DCL)
package com.xuyu.V4; public class SingletonV4 { /** * volatile 禁止指令重排序 */ private static volatile SingletonV4 singletonV4; private SingletonV4(){} public static SingletonV4 getInstance(){ if(singletonV4==null){//第一次判断如果没有创建对象就开始加锁 synchronized (SingletonV4.class){ if (singletonV4==null){//当用户抢到锁,判断初始化 System.out.println("第一次开始创建实例对象,获取到锁了"); try { Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } singletonV4=new SingletonV4(); } } } return singletonV4; } /** * 测试单例 */ public static void main(String[] args) { for (int i=0;i<100;i++){ new Thread(new Runnable() { public void run() { SingletonV4 instance1 = SingletonV4.getInstance(); System.out.println(Thread.currentThread().getName()+","+instance1); } }).start(); } } }
输出结果:线程安全
第一次开始创建实例对象,获取到锁了 Thread-99,com.xuyu.V4.SingletonV4@383a0ba Thread-89,com.xuyu.V4.SingletonV4@383a0ba Thread-92,com.xuyu.V4.SingletonV4@383a0ba Thread-91,com.xuyu.V4.SingletonV4@383a0ba .... Thread-8,com.xuyu.V4.SingletonV4@383a0ba Thread-6,com.xuyu.V4.SingletonV4@383a0ba Thread-9,com.xuyu.V4.SingletonV4@383a0ba Thread-12,com.xuyu.V4.SingletonV4@383a0ba Thread-11,com.xuyu.V4.SingletonV4@383a0ba Thread-10,com.xuyu.V4.SingletonV4@383a0ba Thread-15,com.xuyu.V4.SingletonV4@383a0ba Thread-19,com.xuyu.V4.SingletonV4@383a0ba Thread-16,com.xuyu.V4.SingletonV4@383a0ba
5.静态内部内形式
package com.xuyu.V5; /** * author:须臾 */ public class SingletonV5 { private SingletonV5(){ System.out.println("对象初始化..."); } public static SingletonV5 getInstance(){ return SingletonV5Utils.singletonV5; } /** * 静态内部类方式:能够避免同步带来的效率问题和实现懒加载 */ public static class SingletonV5Utils{ private static SingletonV5 singletonV5=new SingletonV5(); } /** * 测试单例 */ public static void main(String[] args) { System.out.println("项目启动成功。。。"); SingletonV5 instance1 = SingletonV5.getInstance(); SingletonV5 instance2 = SingletonV5.getInstance(); System.out.println(instance1==instance2); } }
输出结果
项目启动成功。。。 对象初始化... true
6.枚举形式
package com.xuyu.V6; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * author:须臾 */ public enum EnumSingleton { INSTANCE; // 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破坏 public void add() { System.out.println("add方法..."); } /** * 测试单例 */ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingleton instance1 = EnumSingleton.INSTANCE; EnumSingleton instance2 = EnumSingleton.INSTANCE; System.out.println(instance1==instance2); Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); EnumSingleton v6 = declaredConstructor.newInstance(); System.out.println(v6==instance1); } }
输出结果:反射破坏不了单例
true Exception in thread "main" java.lang.NoSuchMethodException: com.xuyu.V6.EnumSingleton.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at com.xuyu.V6.EnumSingleton.main(EnumSingleton.java:19)
7.使用容器管理
package com.xuyu.V7; import java.util.HashMap; import java.util.Map; public class SingletonManager { private static Map<String, Object> objMap = new HashMap<String, Object>(); public static void registerService(String key, Object instance) { if (!objMap.containsKey(key)) { objMap.put(key, instance); } } public static Object getService(String key) { { return objMap.get(key); } } }
这种使用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
如何防止破坏单例
虽然单例通过私有构造函数,可以实现防止程序猿初始化对象,但是还可以通过反射和序列化技术破坏单例。
1.使用反射技术破坏单例
// 1. 使用懒汉式创建对象 SingletonV3 instance1 = SingletonV3.getInstance(); // 2. 使用Java反射技术初始化对象 执行无参构造函数 Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); SingletonV3 instance2 = declaredConstructor.newInstance(); System.out.println(instance1 == instance2);
如何防止被反射破坏
私有构造函数
private SingletonV3() throws Exception { synchronized (SingletonV3.class) { if (singletonV3 != null) { throw new Exception("该对象已经初始化.."); } System.out.println("执行SingletonV3无参构造函数..."); } }
2.使用序列化技术破坏单例
Singleton instance = Singleton.getInstance(); FileOutputStream fos = new FileOutputStream("E:\\code\\Singleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("E:\\code\\Singleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); Singleton singleton2 = (Singleton) ois.readObject(); System.out.println(singleton2==instance) //返回序列化获取对象 ,保证为单例 public Object readResolve() { return singletonV3; }
总结
到这里七中写法都介绍完了,至于选择用哪种形式的单例模式,取决于你的项目本身,是否是有复杂的并发环境,还是需要控制单例对象的资源消耗。
(adsbygoogle = window.adsbygoogle || []).push({});
相关文章推荐
- 如何破坏单例模式?如何防止?
- 创造性破坏:世界变化太快,如何不被未来淘汰?
- 如何防止表单重复提交
- 单例模式的七种写法
- 如何防止表单重复提交
- 容器管理:如何防止容器蔓延与成本蔓延
- 如何防止struts配置文件膨胀
- 如何防止XMLHttpRequest对象传回的中文数据中出现乱码
- 如何防止MS Outlook 发送空白主题/主体的邮件?- Avoiding sending empty subject emails in Outlook
- 如何保护.net中的dll文件(防止破解、反编译dll)(转)
- 常见JS挂马方法及如何防止网站被黑客挂马?
- 退出系统后如何防止点击ie后退按钮返回系统
- BCB中如何防止不同模块间单选框RadioButton的互斥---利用TRadioGroup
- 如何防止网页被嵌入框架代码?
- 【广州服务器回收公司】机房旧服务器报废时,如何防止数据泄露,还可以变废为宝?
- iOS应用如何防止被反编译
- VB.NET 如何防止重复打开MDI子窗体
- 如何防止表单重复提交
- 如何防止SWF文件被反编译
- 如何防止服务器被大流量攻击?