单例模式(Singleton)
Singleton模式确保每个Java类加载器只能有一个已有的实例并提供对其的
全局访问
,单例可以节省内存
,加快对象访问速度
适用场景
- 需要频繁实例化然后销毁的对象
- 创建对象耗时长或占用资源大,但又要经常用到的对象
- 带有状态的工具类对象
- 频繁访问数据库或文件的对象,一次实例化多次使用
单例种类
饿汉式
:类加载的时候就被初始化,静态字段中实例化懒汉式
:延迟加载,调用getInstance()方法时实例化双重检查锁
:延迟加载,对上一个进行优化的方式
以上都是线程安全的
代码示例
饿汉式
/**
* 饿汉式,急切初始化静态实例保证线程安全
* <p>
* 优点:没有线程安全问题,简单
* 缺点:提前初始化会延长类加载器加载类的时间;如果不使用会浪费空间;不能传递参数进行实例化
*
* @author July
* @date 2020/10/21
*/
public final class HungrySingleton {
/**
* 类的静态实例,final的
*/
private static final HungrySingleton INSTANCE = new HungrySingleton();
/**
* 私有构造,没有其它方式能初始化类
*/
private HungrySingleton() {
}
/**
* 静态方法提供用户调用以获取类的实例
*
* @return 单例对象
*/
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
懒汉式(私有内部类方式)
/**
* 懒汉式,在需要的时候才初始化单例对象
*
* 优点:延迟加载的情况下解决线程安全问题(Effective Java推荐写法)
*
* @author July
* @date 2020/10/21
*/
public final class LazyLoadedSingleton {
/**
* 私有构造
*/
private LazyLoadedSingleton() {
System.out.println("init LazyLoadedSingleton");
}
/**
* @return 单例对象
*/
public static LazyLoadedSingleton getInstance() {
return HelperHolder.INSTANCE;
}
/**
* 【重点】内部类HelperHolder被引用的时间不会早于getInstance()被调用的时间,
* 所以线程安全,不需要特殊的语言结构(例如volatile或synchronized)。
*/
private static class HelperHolder {
public static final LazyLoadedSingleton INSTANCE = new LazyLoadedSingleton();
}
}
懒汉式(synchronized方式)
/**
* 单例是延迟加载,这里使用synchronized进行同步保证线程安全
* <p>
* 注意:如果是由反射创建的,那么将不会在同一个类加载器中创建一个单例,而是创建多个选项
*
* @author July
* @date 2020/10/21
*/
public final class LazyLoadedSingleton2 {
private static LazyLoadedSingleton2 instance;
private LazyLoadedSingleton2() {
}
/**
* 实例仅在第一次调用时创建
*/
public static synchronized LazyLoadedSingleton2 getInstance() {
if (instance == null) {
instance = new LazyLoadedSingleton2();
}
return instance;
}
}
双重检查锁
/**
* 双重检查锁
* <p/>
* 参考:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
*
* @author July
* @date 2020/10/21
*/
public final class DoubleCheckLockingSingleton {
private static volatile DoubleCheckLockingSingleton instance;
/**
* 私有构造防止客户端实例化
*/
private DoubleCheckLockingSingleton() {
// note 防止通过反射调用实例化
if (instance != null) {
throw new IllegalStateException("Already initialized.");
}
}
/**
* 获取单例对象
*
* @return
*/
public static DoubleCheckLockingSingleton getInstance() {
// 局部变量将性能提高25%,《Effective Java, Second Edition》推荐, p. 283-284
DoubleCheckLockingSingleton result = instance;
// 单例双重检查
// 第一重
if (result == null) {
synchronized (DoubleCheckLockingSingleton.class) {
result = instance;
// 第二重
if (result == null) {
instance = result = new DoubleCheckLockingSingleton();
}
}
}
return result;
}
}
FAQ
- 单例多种实现方式中为什么要用双重检查锁的方式来实现单例?
在延迟加载中,保证线程安全可以直接在getInstance()方法中直接使用synchronized来保证线程安全,但性能一个问题,也就是说每次调用getInstance()方法都需要线程同步(加锁其实只需要在第一次初始化的时候用到),性能不高
更多内容可以参考我的另一篇文章:单例模式之双重检查锁(double check locking)的发展历程
如果你喜欢我的文章,记得一键三连(不要下次一定)