【Java设计模式】单例模式

时间:2021-10-29 01:57:07

### 1. 概述
> 单例模式是确保某一个类中有且只有一个实例。

----------
### 2. 饿汉式单例
``` java
public class SingletonInstance {
private static SingletonInstance mInstance = new SingletonInstance();
// 默认私有构造方法
private SingletonInstance(){}
// 静态工厂方法
public static SingletonInstance getInstance(){
return mInstance ;
}
}
```
在饿汉式单例中,静态变量会在私有构造方法中初始化,这时候唯一的实例就被创建出来了,饿汉式主要使用到的是**空间换时间**的思想,在类还在加载的时候,对象实例便已经创建好了。

----------

### 3. 懒汉式单例
```java
public class SingletonInstance {
private static SingletonInstance mInstance = null;
// 私有默认构造方法
private SingletonInstance(){}
// 静态工厂方法
public static synchronized SingletonInstance getInstance(){
if(mInstance == null){
mInstance = new SingletonInstance();
}
return mInstance;
}
}
```
对于懒汉式单例的处理,使用了``synchronized``参数修饰工厂方法,用来在多线程环境中解决同步问题,懒汉式主要是使用到的是**时间换空间**的思想,在获取实例的时候进行判断,只有在需要的时候才去创建对象,节省内存空间,但是缺点就是实现的方式是线程安全的,这样会降低访问的速度。

----------
### 4. 双重加锁单例
```java
public class SingletonInstance {
private volatile static SingletonInstance mInstance = null;
private SingletonInstance(){}
public static SingletonInstance getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(mInstance == null){
//同步块,线程安全的创建实例
synchronized (SingletonInstance .class) {
//再次检查实例是否存在,如果不存在才真正的创建实例
if(mInstance == null){
mInstance = new SingletonInstance ();
}
}
}
return mInstance;
}
}
```
双重加锁机制指的是,在每次进入``getInstance``方法先不同步,而是进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这属于第一重检查,进入同步块过后再检查实例是否存在,如果不存在,就在同步的情况下创建一个新的实例,这属于第二重检查。这样便只需要同步一次,并减少了在多次同步情况下进行判断浪费的时间。
这种实现方式会使用到**volatile**关键字,意思是被**volatile**修饰的变量的值,不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而保证线程正确的处理该变量。
> **volatile**关键字在**JDK5**之前的版本中加锁失败,注意之。而且**volatile**关键字会屏蔽掉虚拟机中的一些必要的代码优化,因此虽然能实现双重检查加锁机制的单例,但并不建议大量采用。

那有什么方案可以既能达到延迟加载,又能实现线程安全的目的呢?

-----
### 5. Lazy Initialization Holder Class模式
```java
public class SingletonInstance {

private SingletonInstance(){}
/**
* 类级的内部类的实例与外部类的实例没有绑定关系,而且只有被调用到时才会装载
*/
private static class SingletonHolder{
/**
* 静态初始化,由JVM来保证线程安全
*/
private static SingletonInstance mInstance = new SingletonInstance();
}
public static SingletonInstance getInstance(){
return SingletonHolder.mInstance;
}
}
```
如果只是想简单的实现线程安全的单例,可以使用之前的**饿汉式**方式。但是缺点就是会在类装载的时候初始化对象,造成空间的浪费。
那么只要解决了类加载时自动初始化对象的问题,便可以解决问题。因此可以采用类级内部类的方式去实现,这样的话,只有在需要的时候才会创建对象实例,也达到了延迟加载和线程安全的目的。
从实现过程来看,但调用`getInstance`方法时,它会读取`SingletonHolder.mInstance`,从而初始化,在这个类被装载的时候,也会初始化静态成员,而由于静态域的特性,只会初始化一次,并且由JVM来保证线程安全。
>- 什么是类级内部类?
被`static`修饰的成员内部类才是类级内部类,如果没有被`static`修饰则被称为对象内部类,而且类级内部类与外部类对象不存在依赖关系,只有在第一次使用的时候才会被调用。
>- 多线程默认同步锁知识?
在多线程开发中,我们主要使用`synchronized`来对互斥锁进行同步控制,但是某些情况下JVM已经为我们进行了同步控制了,主要有:
1. 静态初始化方法初始化数据时;
2. 访问`final`字段时;
3. 在创建线程之前创建对象时;
4. 线程可以看见要处理的对象时;

----------
### 6. 枚举式单例
```java
public enum SingletonInstace{
// 定义一个枚举元素,它代表了一个实例
mInstance;
// 单例的操作
public void singletonOperation(){

}
}
```
使用枚举的方式既使得代码简洁,而且也由JVM来保证序列化机制,防止多次实例化,是最佳的实现单例的方式。