单例模式
亦称: 单件模式、Singleton
1. 简介
单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
2. 模拟场景
单例模式同时解决了两个问题, 所以违反了单一职责原则:
- 保证一个类只有一个实例。为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件)的访问权限。
它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。
注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。 - 为该实例提供一个全局访问节点。还记得你 (好吧, 其实是我自己)用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。 和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。但是它可以保护该实例不被其他代码覆盖。 还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。
3. 解决方案
所有单例的实现都包含以下两个相同的步骤:
将默认构造函数设为私有, 防止其他对象使用单例类的new运算符。 新建一个静态构建方法作为构造函数。该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。无论何时调用该方法, 它总是会返回相同的对象。
总结
单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
单例拥有与全局变量相同的优缺点。尽管它们非常有用, 但却会破坏代码的模块化特性。
在某些其他上下文中, 你不能使用依赖于单例的类。你也将必须使用单例类。绝大多数情况下, 该限制会在创建单元测试时出现。
4. 真实世界类比
政府是单例模式的一个很好的示例。一个国家只有一个官方政府。不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。
5. Java中的例子
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
- Spring通过ConcurrentHashMap实现单例AbstractBeanFactory#getBean()
6. 单例模式的实现
java
public class Singleton_01 {
private static Singleton_01 instance;
private Singleton_01() {
}
public static Singleton_01 getInstance() {
if (null != instance) return instance;
instance = new Singleton_01();
return instance;
}
}
java
/**
此种模式虽然是安全的,但由于把锁加到⽅法上后,所有的访问都因需要锁占⽤导致资源的浪费。
如果不是特殊情况下,不建议此种⽅式实现单例模式。
*/
public class Singleton_02 {
private static Singleton_02 instance;
private Singleton_02() {
}
public static synchronized Singleton_02 getInstance() {
if (null != instance) return instance;
instance = new Singleton_02();
return instance;
}
}
java
/**
双重锁的⽅式是⽅法级锁的优化,减少了部分获取实例的耗时。
同时这种⽅式也满⾜了懒加载。
*/
public class Singleton_06 {
private static Singleton_06 instance;
private Singleton_06() {
}
public static Singleton_06 getInstance(){
if(null != instance) return instance;
synchronized (Singleton_06.class){
if (null == instance){
instance = new Singleton_06();
}
}
return instance;
}
}
java
/**
此种⽅式在程序启动的时候直接运⾏加载,后续有外部需要使⽤的时候获取即可。
*/
public class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();
private Singleton_03() {
}
public static Singleton_03 getInstance() {
return instance;
}
}
java
/**
使⽤类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,
同时不会因为加锁的⽅式耗费性能。这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,
也就是⼀个类的构造⽅法在多线程环境下可以被正确的加载。
此种⽅式也是⾮常推荐使⽤的⼀种单例模式
*/
public class Singleton_04 {
private static class SingletonHolder {
private static Singleton_04 instance = new Singleton_04();
}
private Singleton_04() {
}
public static Singleton_04 getInstance() {
return SingletonHolder.instance;
}
}
java
/**
1. java并发库提供了很多原⼦类来⽀持并发访问的数据安全性;
AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference。
2. AtomicReference可以封装引⽤⼀个V实例,⽀持并发访问如上的单例⽅式就是使⽤了这样的⼀个特点。
3. 使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖
于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外
的开销,并且可以⽀持较⼤的并发性。当然CAS也有⼀个缺点就是忙等,如果⼀直没有获取到将会处于死循环中。
*/
public class Singleton_06 {
private static final AtomicReference<Singleton_06> INSTANCE = new
AtomicReference<Singleton_06>();
private static Singleton_06 instance;
private Singleton_06() {
}
public static final Singleton_06 getInstance() {
for (; ; ) {
Singleton_06 instance = INSTANCE.get();
if (null != instance) return instance;
INSTANCE.compareAndSet(null, new Singleton_06());
return INSTANCE.get();
}
}
}
java
public enum Singleton_08 {
/**操作失败**/
RC999("999","操作XXX失败"),
/**操作成功**/
RC200("200","success"),
/**服务降级**/
RC201("201","服务开启降级保护,请稍后再试!"),
/**热点参数限流**/
RC202("202","热点参数限流,请稍后再试!");
/**自定义状态码**/
private final String code;
/**自定义描述**/
private final String message;
Singleton_08(String code, String message){
this.code = code;
this.message = message;
}
}
// 还有就是可以像前面的单例一样有对象方法使用
public enum Singleton_09 {
Instance;
public void business() {
System.out.println("hi~");
}
public static void main(String[] args) {
// 调用枚举的业务方法
Singleton_09.Instance.business();
}
}