一、单例模式
单例模式是设计模式之一,单例模式要求代码中的某个类,只能有一个实例,不能有多个。这种单例模式在开发中非常常见,例如 JDBC 中的 DataSource 这样的对象等等。
单例模式具体实现方式分为:饿汉模式 和 懒汉模式
- 饿汉模式:中午吃完饭,用了 3 个碗,就立即把这 3 个碗洗了。(着急)
- 懒汉模式:中午吃完饭,用了 3 个碗,先不洗。到了晚上吃饭时候,只需要 2 个碗,然后就只洗 2 个碗。(懒汉)(计算机中更加高效)
饿汉的单例模式是比较着急的去进行创建实例。
懒汉的单例模式是不太着急的去创建实例,只是在用的时候才真正创建。
1. 饿汉模式
类加载阶段,就会直接创建实例。(static)
static 修饰的成员更准确的来说,应该叫做“类成员” -> “类属性/类方法”。一个java程序中,一个类对象只存在一份(JVM保证的),进一步就保证了类的 static 成员也是只有一份。
不加static 修饰的成员,叫做“实例成员” -> “实例属性/实例方法”。
class Singleton {
//1. 使用 static 创建一个实例,并且立即进行实例化
//这个 instance 对应的实例,就是该类的唯一实例
private static Singleton instance = new Singleton();
//2. 为了防止程序员在其他地方不小心 new 了这个 Singleton,所以可以把构造方法设置为 private
private Singleton() {}
//3. 提供一个方法,让外面能够拿到这个唯一实例 instance
public static Singleton getInstance() {
return instance;
}
}
饿汉模式要注意:
- ① 提前创建出来一个实例,并进行初始化
- ② 将构造方法私有化
- ③ 提供一个方法,来获取这个实例
2. 懒汉模式
类加载的时候不创建实例,第一次用的时候才创建实例。
根据基本思路:先大概写出来代码(不是最终代码)
- ① 提前创建出来一个实例,并进行初始化
- ② 将构造方法私有化
- ③ 提供一个方法,来获取这个实例
class Singleton2 {
//1. 不立即就初始化实例
private static Singleton2 instance = null;
//2. 把构造方法设为 private
private Singleton2() {}
//3. 提供一个方法来获取上述单例的实例
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
上述代码是线程不安全的,我们需要考虑:
1. 在 getInstance 方法中,用到了 if 条件判断语句,结构内部又进行了修改操作。因此可以看做是读操作和修改(写)操作。在多线程中,写操作不是原子性的(包含了 3 条操作),因此会出现线程不安全的情况。所以我们需要用 加锁 synchronized 关键字将这两条语句进行捆绑,保证原子性。(锁对象:类对象)
2. 在 getInstance 方法中, if 条件判断语句中的判断条件需要一直连续的读取 instance 的值,这就会导致编译器的优化。所以我们需要用 volatile 关键字来禁止编译器对 instance 值读取的优化,保证内存可见性。
3. 考虑两种情况:
(1)当instance == null,即还没有被初始化时,频繁获取锁再判断是必要的。
(2)当 instance 已经初始化过了,再使用 getInstance 方法获取实例时,直接返回 instance 的值即可。而如果像上述代码,还要频繁的获取锁,加锁,进行读操作,会大大降低运行效率。所以我们需要在 synchronized 外再进行一次判断(instance == null),即双重 if 判定。避免无脑加锁现象,进而提高运行效率。
- 如果为 null,说明是第一次,进行加锁判断、初始化。
- 如果不为null,说明已经初始化过,直接返回 instance。(不进入外层 if 内部,即不进行获取锁的操作)
正确的懒汉模式:
class Singleton2 {
//1. 不立即就初始化实例
private static volatile Singleton2 instance = null;
//2. 把构造方法设为 private
private Singleton2() {}
//3. 提供一个方法来获取上述单例的实例
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (Singleton2.class) {
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
}
懒汉模式要注意:
- ① 正确的位置加锁
- ② volatile
- ③ 双重 if 判定