一、单例模式介绍
1. 解决的问题
- 保障一个类只有一个实例。 最常见的是管制某些共享资源 (例如数据库或文件) 的拜访权限。
运作形式是这样的: 如果创立了一个对象,同时过一会儿后决定再创立一个新对象,此时会取得之前已创立的对象, 而不是一个新对象。
留神, 一般构造函数无奈实现上述行为,因为构造函数的设计决定了它必须总是返回一个新对象。
- 为该实例提供一个全局拜访节点。 还记得用过的那些存储重要对象的全局变量吗?它们在应用上非常不便,但同时也十分不平安,因为任何代码都有可能笼罩掉那些变量的内容,从而引发程序解体。
和全局变量一样, 单例模式也容许在程序的任何中央拜访特定对象。同时能够爱护该实例不被其余代码笼罩。
2. 定义
单例模式是一种创立型设计模式,可能保障一个类只有一个实例,并提供一个拜访该实例的全局节点。
3. 利用场景
- 程序中的某个类对于所有客户端只有一个可用的实例,能够应用单例模式。
- 要更加严格地管制全局变量,能够应用单例模式。
留神:能够随时调整限度并设定生成单例实例的数量,只需批改 获取实例
办法,即 getInstance
中的代码即可实现。
4. 与其余设计模式的关系
- 形象工厂模式、生成器模式和原型模式都能够用单例来实现。
- 外观模式类通常能够转换为单例模式类,因为在大部分状况下一个外观模式就足够了。
如果能将对象的所有共享状态简化为一个享元对象,那么享元模式就和单例模式相似。但这个两个设计模式有两个根本性的不同。
- 只会有一个单例实体,但享元类能够有多个实体,各实体的外在状态也能够不同。
- 单例对象能够是可变的。享元对象是不可变的。
二、单例模式优缺点
1. 长处
- 能够保障一个类只有一个实例。
- 取得了一个指向该实例的全局拜访节点。
- 仅在首次申请单例对象时对其进行初始化。
2. 毛病
- 违反了繁多职责准则。
- 单例模式可能覆盖不良设计,比方程序各组件之间相互了解过多等。
- 该模式在多线程环境下须要进行非凡解决,防止多个线程多次出创立单例对象。
- 单例的客户端代码单元测试可能会比拟艰难,因为许多测试框架以基于继承的形式创立模仿对象。因为单例类的构造函数是公有的,而且绝大部分语言无奈重写静态方法,所以须要认真思考模仿单例的办法。
三、7 种单例模式实现
动态类应用
/** * 动态类单例实现 */public class StaticSingleton { public static Map<String,String> hashMap = new ConcurrentHashMap<String, String>();}
- 这种形式在咱们的业务开发场景中十分常见,这样的形式能够在第一次运行的时候间接初始化 Map 类。
- 在不须要维持任何状态,不须要提早加载,仅仅用于全局拜访时,这种形式更便于应用。
- 但在须要被继承、须要维持一些特定状态时,单例模式更适宜。
单例模式的实现形式比拟多,次要是实现上是否反对懒汉模式、是否线程平安。
接下来通过不同的单例模式实现形式来解析单例模式。
1. 懒汉单例模式(线程不平安)
/** * 懒汉单例模式(线程不平安) */public class SlobThreadUnsafeSingleton { private static SlobThreadUnsafeSingleton instance; /** * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法 */ private SlobThreadUnsafeSingleton() { } public static SlobThreadUnsafeSingleton getInstance() { if (ObjectUtils.isEmpty(instance)) { instance = new SlobThreadUnsafeSingleton(); } return instance; }}
这种懒汉单例模式满足了懒加载,但当多个访问者同时获取对象实例时(多过程),会导致多个实例并存,没有达到单例模式的需要。
2. 懒汉单例模式(线程平安)
/** * 懒汉单例模式(线程平安) */public class SlobThreadSafeSingleton { private static SlobThreadSafeSingleton instance; /** * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法 */ private SlobThreadSafeSingleton() { } public static synchronized SlobThreadSafeSingleton getInstance() { if (ObjectUtils.isEmpty(instance)) { instance = new SlobThreadSafeSingleton(); } return instance; }}
这种懒汉单例模式是线程平安的,但因为锁在办法上,所有的拜访都须要锁占用,会导致资源的节约。非非凡状况,不倡议应用此种形式来实现单例模式。
3. 饿汉单例模式(线程平安)
/** * 饿汉单例模式(线程平安) */public class EagerSingleton { private static EagerSingleton instance = new EagerSingleton(); /** * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法 */ private EagerSingleton() { } private static EagerSingleton getInstance() { return instance; }}
饿汉单例模式并不是懒加载,简略来说就是无论是否用到该类都会在程序启动之初创立。这种形式会导致的问题相似一关上某个软件,手机间接卡死(开启了过多无用性能,导致内存不足)。
4. 双重校验锁单例模式(线程平安)
/** * 双重校验锁单例模式(线程平安) */public class DoubleCheckingLockingSingleton { private static volatile DoubleCheckingLockingSingleton instance; /** * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法 */ private DoubleCheckingLockingSingleton() { } public static DoubleCheckingLockingSingleton getInstance() { if (ObjectUtils.isNotEmpty(instance)) { return instance; } synchronized (DoubleCheckingLockingSingleton.class) { if (ObjectUtils.isEmpty(instance)) { instance = new DoubleCheckingLockingSingleton(); } } return instance; }}
双重校验锁形式实现的单例模式是办法级锁的优化,缩小了局部获取实例的耗时,同时也满足了懒加载。
5. 动态外部类单例模式(线程平安)
/** * 动态外部类单例模式(线程平安) */public class StaticInnerSingleton { private static class SingletonHolder { private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton(); } /** * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法 */ private StaticInnerSingleton(){ } public static final StaticInnerSingleton getInstance() { return SingletonHolder.INSTANCE; }}
动态外部类实现的单例模式,既保证了线程平安,也保障了懒加载,同时不会因为加锁的形式导致性能开销过大。
这次要是因为 JVM 虚拟机能够保障多过程并发拜访的正确性,即一个类的构造方法在多线程环境下,能够被正确加载。
因而,动态类外部类的实现形式十分举荐应用。
6. CAS「AtomicReference」单例模式(线程平安)
/** * CAS「AtomicReference」单例模式(线程平安) */public class CompareAndSwapSingleton { private static final AtomicReference<CompareAndSwapSingleton> INSTANCE = new AtomicReference<CompareAndSwapSingleton>(); private static CompareAndSwapSingleton instance; /** * 单例的构造函数必须永远是公有类型,以避免应用`new`运算符间接调用构造方法 */ private CompareAndSwapSingleton() { } public static final CompareAndSwapSingleton getInstance() { for (; ; ) { CompareAndSwapSingleton instance = INSTANCE.get(); if (ObjectUtils.isNotEmpty(instance)) { return instance; } INSTANCE.compareAndSet(null, new CompareAndSwapSingleton()); return INSTANCE.get(); } }}
Java 并发库提供了很多原子类来反对并发拜访的数据安全性:AtomicInteger
、 AtomicBoolean
、 AtomicLong
、AtomicReference
。
AtomicReference
能够封装援用一个实例,反对并发拜访,该单例形式就是应用该特点实现。
采纳 CAS 形式的长处就是不须要传统的加锁形式来保障线程平安,而是依赖 CAS 的忙等算法,依赖底层硬件的实现,来保障线程平安。绝对于其余锁的实现没有线程的切换和阻塞,也就没有了额定开销,因而能够反对较大的并发。
当然,CAS 形式也存在毛病,忙等即如果始终没有获取到将会处于死循环中。
7. 枚举单例模式(线程平安)
/** * 枚举单例(线程平安) */public enum EnumSingleton { INSTANCE; public void whateverMethod() { System.out.println("enum singleton method"); }}
约书亚·布洛克(英语:Joshua J. Bloch,1961年8⽉28⽇-),美国驰名程序员,《Effective Java》作者。他为Java平台设计并实作了许多的性能,曾负责Google的⾸席Java架构师(Chief Java Architect)。
《Effective Java》作者约书亚·布洛克举荐应用枚举的形式解决单例模式,这种形式应该是平时起码用到的。
这种形式解决的单例模式的次要问题:线程平安、自在串行化、繁多实例。
调用形式
@Testpublic void testEnumSingleton(){ EnumSingleton.INSTANCE.whateverMethod();}
这种写法在性能上与共有域⽅法相近,然而它更简洁,⽆偿地提供了串⾏化机制,相对防⽌对此实例化,即便是在⾯对简单的串⾏化或者反射攻打的时候。尽管这种形式还没有⼴泛采⽤,然而单元素的枚举类型曾经成为实现单例 的最佳⽅法。
但这种⽅式在存在继承场景下是不可⽤的。
四、单例模式构造
- 单例(Singleton)类申明了一个名为
getInstance
获取实例的静态方法来返回其所属类的一个雷同实例。 - 单例的结构模式必须对客户端(Client)代码暗藏。调用
获取实例
办法必须是获取单例对象的惟一形式。
设计模式并不难学,其自身就是多年教训提炼出的开发指导思想,关键在于多加练习,带着应用设计模式的思维去优化代码,就能构建出更正当的代码。
源码地址:https://github.com/yiyufxst/d...参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei...
深刻设计模式:https://refactoringguru.cn/de...