— 设计模式 — 1 min read
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并对整个系统提供这个实例。)
Singleton:单例类
Singleton 类通过通过 private 修饰的构造函数使该类不可以被外部实例化,从而保证全局唯一的实例。
在系统中,要求该类的对象有且仅有一个,多个的情况下可能会出现性能下降等不良反应,此时就应该使用 Singleton 。
由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。
单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中
在单例使用过程中,要注意在并发的情况下线程同步问题!避免在多线程的情况下产生多个单例对象,这样就失去了单例的本质。
具体解决方案将会在实现中具体说明,主要依赖 Synchronize 实现同步,再就是通过枚举类保证。
同时在针对单例可能会被反射和序列化破坏,导致单例失效。
如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。具体查看[饿汉模式]
单例模式实现有多重方式,其中包括线程安全和线程不安全两大类。
1public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4
5 public static Singleton getInstance() { 6 //在用到时才会被初始化7 if (instance == null) { 8 instance = new Singleton(); 9 } 10 return instance; 11 } 12}
这种方式能够保证在多线程的情况下,保证安全,而且也能保证只有在用到时才会被初始化,但问题的对整个方法同步性能很差。
1public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 //synchronized进行同步5 public static synchronized Singleton getInstance() { 6 //在用到时才会被初始化7 if (instance == null) { 8 instance = new Singleton(); 9 } 10 return instance; 11 } 12}
该种方式是在类被加载是就已经进行了初始化,借助于类加载机制保证了线程安全,避免了在多线程的情况下需要进行同步,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
1public class Singleton { 2 private static Singleton instance = new Singleton(); 3 private Singleton (){} 4 public static Singleton getInstance() { 5 return instance; 6 } 7}
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟饿汉模式不同的是(很细微的差别):饿汉模式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比饿汉模式就显得很合理。
1public class Singleton{2 private Singleton(){}3 private static class SingletonHolder{4 private static final Singleton instance = new Singleton();5 }6 public static Singleton getInstance(){7 return SingletonHolder.instance;8 }9}
目前被推荐最多的实现方式,同样在第三版的《Effective Java》中被大佬推荐的方式,由于枚举类型的天生线程安全的优势,避免了手动同步,而且在其他实现方式中可以被反序列化的问题也得到了解决。墙裂推荐。
1public emum Singleton{2 INSTANCE;3 public void udf(){4 }5}
1public class Singleton { 2 private volatile static Singleton singleton; 3 private Singleton (){} 4 public static Singleton getSingleton() { 5 if (singleton == null) { 6 synchronized (Singleton.class) { 7 if (singleton == null) { 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14}
单例模式作为最普遍的设计模式,无论在实践还是面试中都会经常遇到,可具体分为五种,懒汉模式、饿汉模式、静态内部类模式、枚举模式、双重检查🔒模式。需要重点掌握枚举模式、双重检查🔒模式
设计模式 设计模式之禅 [转+注]单例模式的七种写法-HollisChuang's Blog 深度分析Java的枚举类型—-枚举的线程安全性及序列化问题-HollisChuang's Blog