认识单例
单例模式是一种常用的软件设计模式,它的核心作用就是保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖的对象时,
则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
常见应用场景:
- Windows的TaskManager (任务管理器)典型的单例模式
- 项目中读取配置文件的类,一般也只有一个对象
- 数据库连接池的设计一般也是采用单例模式
- Sping中,每个Bean默认是单例的,这样Spring容器就可以方便管理
- 在Spring MVC和Structs1框架中,控制器对象也是单例
常见的五种实现单例的方式及特点:
- 饿汉式(线程安全,调用效率高,但是,不能延时加载)
- 懒汉式(线程安全,调用效率不高,但是,可以延时加载)
- 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)
- 静态内部类式(线程安全,调用效率高,但是,可以延时加载)
- 枚举单例(线程安全,调用效率高,但不能延时加载)
饿汉式:
1 public class SingletonDemo { 2 3 //类初始化时,立即加载这个对象。由于JVM加载类时是天然线程安全的,所以保证了饿汉式是线程安全的 4 private static SingletonDemo instance = new SingletonDemo(); 5 6 //私有化构造器 7 private SingletonDemo(){ 8 9 } 10 //方法没有用synchronized 修饰,调用效率高 11 public static SingletonDemo getInstance(){ 12 return instance; 13 } 14 }
懒汉式:
1 public class SingletonDemo02{ 2 3 private static SingletonDemo02 instance; 4 5 private SingletonDemo02(){ 6 7 } 8 public static synchronized SingletonDemo02 getInstance(){ 9 if(instance == null){ 10 instance = new SingletonDemo02(); 11 } 12 return instance; 13 } 14 }
双重检测锁式:
1 public class SingletonDemo03{ 2 private static SingletonDemo03 instance = null; 3 4 public SingletonDemo03(){ 5 6 throw new RuntimeException(); 7 } 8 9 //由于编译器优化和JVM底层内部模型原因,偶尔会导致同步块失效,不建议使用 10 public static SingletonDemo03 getInstance(){ 11 if(instance == null){ 12 SingletonDemo03 sl; 13 synchronized (SingletonDemo03.class){ 14 sl = instance; 15 if(sl == null){ 16 synchronized (SingletonDemo03.class){ 17 if(sl == null){ 18 sl = new SingletonDemo03(); 19 } 20 } 21 instance = sl; 22 } 23 } 24 } 25 return instance; 26 } 27 }
静态内部类式:
1 public class SingletonDemo04{ 2 3 private static class SingletonClassInstance{ 4 private static final SingletonDemo04 instance = new SingletonDemo04(); 5 private Object readResolve() throws ObjectStreamException{ 6 return instance ; 7 } 8 } 9 public static SingletonDemo04 getInstance(){ 10 return SingletonClassInstance.instance; 11 } 12 private SingletonDemo04(){ 13 14 } 15 }
特点:
- 外部类没有Static 属性,则不会像饿汉式那样立即加载对象
- 只有真正调用getInstance()时,才会加载静态内部类。instance是staic final类型,保证该实例在内存中的唯一性,而且只能被赋值一次,从而保证了线程安全性
- 兼备了并发高效调用和延迟加载的优势。
枚举单例:
1 public enum SingletonDemo05{ 2 //枚举元素,本身就是单例对象 3 INSTANCE; 4 5 //添加需要的操作方法 6 public void singletonOperation(){ 7 8 } 9 }
到这里介绍完五种单例的实现方式,本文也预示着结束,然而除了枚举方式实现的单例其它四种方式仍然存在被破解的可能。
通过反射和反序列化都可以破解除枚举之外四种实现单例的模式,使用它们可以使实现单例的类同时存在多个实例,当然也有防止它们破解的办法。
在私有构造器中添加 throw new RuntimeException() 这句代码即可防止通过反射方式破解单例模式。
防止反序列化破解需在类中添加下面这个方法:
1 //防止通过反序列化破坏单例模式,定义该方法之后,在反序列化时,会直接返回该方法指定的对象,不会再单独创建新对象 2 private Object readResolve() throws ObjectStreamException{ 3 return instance ; 4 }
推荐选用原则:
当单例对象占用资源少,且不需要延时加载时,枚举式 好于 饿汉式
当单例对象占用资源大,且需要延时加载时:静态内部类式 好于 懒汉式