>关于单例设计模式的内容。
## 单例模式介绍
单例模式可以说是整个设计中最简单的模式之一,而且这种方式即使在没有看设计模式相关资料也会常用在编码开发中。
因为在编程开发中经常会遇到这样一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,并需要提供一个全局访问此实例的点。
综上以及我们平常的开发中,可以总结一条经验,单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能。
## 7种单例模式实现
单例模式的实现方式比较多,主要在实现上是否支持懒汉模式、是否支持线程安全中运用各种技巧。也有一些场景会直接使用 static 静态类或属性和方法的方式进行处理,供外部调用。
接下来介绍不同方式的实现。
### 0. 静态类使用
```
public class Singleton_0 {
public static Map<String, String> cache = new ConcurrentHashMap<>();
}
```
- 静态类可以在第一次运行时直接初始化Map类,同时也不需要到延迟加载中使用
- 在不需要维持任何状态的情况下,仅仅用于全局访问,使用静态类的方法更加方便
- 如果需要被继承以及需要一些特定状态的情况下,就适合使用单例模式
### 1. 懒汉模式(线程不安全)
```
public class Singleton_1 {
private static Singleton_1 instance;
private Singleton_1() {
}
public static Singleton_1 getInstance() {
if (null != instance) return instance;
instance = new Singleton_1();
return instance;
}
}
```
- 单例模式有一个特点就是不允许外部创建,也就是new Singleton_1(), 因此这里默认的构造函数加上了私有属性private。
- 目前这种方式确实满足了懒加载,但是如果有多个访问者同时去获取对象实例,就会造成多个同样的实例并存。
### 2. 懒汉模式(线程安全)
```
public class Singleton_2 {
private static Singleton_2 instance;
private Singleton_2() {
}
public static synchronized Singleton_2 getInstance() {
if (null != instance) return instance;
instance = new Singleton_2();
return instance;
}
}
```
- 这种方式虽然是安全的,但是由于把锁加载方法上,所有的方法都因需要锁占用导致资源的浪费。如果不是特殊情况,不建议这种方式实现单例模式
### 3. 饿汉模式(线程安全)
```
public class Singleton_3 {
private static Singleton_3 instance = new Singleton_3();
private Singleton_3() {
}
public static Singleton_3 getInstance() {
return instance;
}
}
```
- 这种方式与第一个实例化Map基本一致,在程序启动的时候直接运行加载,后续有外部有需要使用直接获取即可
- 但是这种方式不是懒加载,也就是说无论程序中是否用到这种的类都会在程序启动时进行创建
- 这种方式会导致就像下载个游戏软件,可能游戏地图还没打开,但是程序已经将这些地图全部要实例化。到你手机上最明显的体验就是一开游戏内存满了,手机卡了,需要换了。
### 4. 使用类的内部类(线程安全)
```
public class Singleton_4 {
private static class SingleHolder {
private static Singleton_4 instance = new Singleton_4();
}
private Singleton_4() {
}
public static Singleton_4 getInstance() {
return SingleHolder.instance;
}
}
```
- 使用类的静态类实现的单例模式,既可以保证线程安全,又保证了懒加载,同时不会因为加锁的方式耗费性能
- 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确加载
- 这种方式也是非常推荐使用的一种单例模式
### 5. 双重锁校验(线程安全)
```
public class Singleton_5 {
private static volatile Singleton_5 instance;
private Singleton_5() {
}
public static Singleton_5 getInstance() {
if (null != instance) return instance;
synchronized (Singleton_5.class) {
if (null == instance) {
instance = new Singleton_5();
}
}
return instance;
}
}
```
- 双重锁的方式是方法锁的优化,减少了部分获取实例的耗时
- 同时这种方式也满足了懒加载
### 6. CAS [AtomicReference](线程安全)
```
public class Singleton_6 {
private static final AtomicReference<Singleton_6> INSTANCE = new AtomicReference<Singleton_6>();
private static Singleton_6 instance;
private Singleton_6() {
}
public static Singleton_6 getInstance() {
for(;;) {
Singleton_6 instance = INSTANCE.get();
if (null != instance) return instance;
INSTANCE.compareAndSet(null, new Singleton_6());
return INSTANCE.get();
}
}
public static void main(String[] args) {
System.out.println(Singleton_6.getInstance());
System.out.println(Singleton_6.getInstance());
}
}
```
- Java并发库提供了很多原子类来支持并发访问的数据安全性;如AtomicInteger、 AtomicBoolean、 AtomicLong、 AtomicReference
- **AtomicReference** 可以封装引用一个V实例,支持并发访问。如上的单例方式就是使用了这个特点
- 使用CAS的好处是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,;来保证线程安全;相对于其他锁的实现方式没有线程的切换和阻塞,也就没有了额外的开销,并且可以支持较大并发性
- CAS的缺点就是忙等,如果一直没有获取到就会处于死循环中
### 7. Effective Java作者推荐的枚举单例(线程安全)
```
public enum Singleton_7 {
INSTANCE;
public void doSomething() {
System.out.println("hi~");
}
}
```
- Effective Java 作者推荐使用枚举的方式解决单例模式,此种方式可能是平时最少用到的。
- 这种方式解决了最主要的;线程安全、自由串行化、单一实例。
>约书亚·布洛克(英语:Joshua J. Bloch,1961年8月28日-),美国著名程序员。他为Java平台设计并实作了许多的功能,曾担任Google的首席Java架构师(Chief Java Architect)。
调用方式
```
public class Test {
public static void main(String[] args) {
Singleton_7.INSTANCE.doSomething();
}
}
```
*这种写法在功能上与共有域方法相近,但是它最简洁,无偿提供了串行化机制,绝对防止此类实例化,即使是在面对复杂的串行化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经实现了Singleton的最佳方法。*
此种方法在继承场景下不可用
## 总结
虽然只是一个很平常的单例模式,但在各种的实现上真的可以看到java的基本功的体现,这里包括了;懒汉、饿汉、线程是否安全、静态类、内部类、加锁、串行化等等。
在平时的开发中如果可以确保此类是全局可用不需要做懒加载,那么直接创建并给外部调用即可。但如果是很多的类,有些需要在用户触发一定的条件后(游戏关卡)才显示,那么一定要用懒加载。线程的安全上可以按需选择。

【设计模式】单例设计模式