什么是单例模式
单例模式,是一种经典的设计模式。
设计模式: 在软件开发中会有一些常见的场景,针对这些场景,大佬们总结了一些固定套路,按照套路来实现代码,不容易出错。
单例就是单个实例,单例模式可以保证某一个类在一个程序中只存在一份实例,不会创建多个实例。
实现单例模式
Java 单例模式的实现有很多种写法,本文只介绍两个,饿汉模式 和 懒汉模式。
借助 Java 语法来保证某个类,只能有一个实例,不能 new 多个实例。
饿汉模式
类加载的同时,立马创建实例
class Singleton {
// 类加载的时候就创建实例
private static Singleton instance = new Singleton();
// 设为私有, 使类外不能new
private Singleton() {}
// 静态方法
public static Singleton getInstance() {
return instance;
}
}
因为没有涉及修改操作,这个已经是线程安全的。
懒汉模式
类加载的时候不创建实例,等到要使用的时候才创建实例
class Singleton {
// 类加载的时候没有创建实例
private static Singleton instance = null;
// 禁止外部创建对象
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
// 用到的时候才创建实例
instance = new Singleton();
}
return instance;
}
}
上面这个懒汉模式的实现是线程不安全的,在首次创建实例时,如果多个线程调用 getInstance 方法,可能会创建出多个实例。
给 getInstance 方法加上 synchronized
可以解决这个线程安全问题。
class Singleton {
// 类加载的时候没有创建实例
private static Singleton instance = null;
// 禁止外部创建对象
private Singleton() {}
synchronized public static Singleton getInstance() {
if (instance == null) {
// 用到的时候才创建实例
instance = new Singleton();
}
return instance;
}
}
但是这样会大大降低效率,因为只有首次创建实例时,才会出现线程安全问题,而在方法上加锁后,任何时候调用 getInstance 都会触发锁竞争,而且加锁 / 解锁的开销也比较高。
解决办法:if 语句判断是否加锁
class Singleton {
// 类加载的时候没有创建实例
private static volatile Singleton instance = null;
// 禁止外部创建对象
private Singleton() {}
public static Singleton getInstance() {
// 判断是否加锁
if (instance == null) {
synchronized (Singleton.class) {
// 获取到锁后,可能已经隔了一段时间
// 需要再判断一下 instance 是否为空
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
上述代码是改进后的线程安全的懒汉模式
1. 使用双重 if
判定
第一重判断是否加锁,第二重是获取到锁后,判断是否要创建实例(多个线程同时通过第一重判定,同时竞争锁,竞争成功的线程创建了实例,后面再拿到锁的线程就被第二重判定挡住了)。
2. 给 instance 加上了 volatile
保证 instance 的内存可见性,避免读取 instance 时出现误差。