一、单例模式介绍
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》作者约书亚·布洛克举荐应用枚举的形式解决单例模式,这种形式应该是平时起码用到的。
这种形式解决的单例模式的次要问题:线程平安、自在串行化、繁多实例。
调用形式
@Test
public void testEnumSingleton()
{EnumSingleton.INSTANCE.whateverMethod();
}
这种写法在性能上与共有域⽅法相近,然而它更简洁,⽆偿地提供了串⾏化机制,相对防⽌对此实例化,即便是在⾯对简单的串⾏化或者反射攻打的时候。尽管这种形式还没有⼴泛采⽤,然而单元素的枚举类型曾经成为实现单例 的最佳⽅法。
但这种⽅式在存在继承场景下是不可⽤的。
四、单例模式构造
- 单例(Singleton)类申明了一个名为
getInstance
获取实例的静态方法来返回其所属类的一个雷同实例。 - 单例的结构模式必须对客户端(Client)代码暗藏。调用
获取实例
办法必须是获取单例对象的惟一形式。
设计模式并不难学,其自身就是多年教训提炼出的开发指导思想,关键在于多加练习,带着应用设计模式的思维去优化代码,就能构建出更正当的代码。
源码地址:https://github.com/yiyufxst/d…
参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei…
深刻设计模式:https://refactoringguru.cn/de…