文章目录
原文地址: 文章来源于Java中文社群公众号:单例模式,真不简单
前言
单例模式无论在我们面试,还是日常工作中,都会面对的问题。但很多单例模式的细节,值得我们深入探索一下。
1 什么是单例模式
单例模式是一种非常常用的软件设计模式,它定义是单例对象的类只能允许一个实例存在。
该类负责创建自己的对象,同时确保只有一个对象被创建。一般常用在工具类的实现或创建对象需要消耗资源的业务场景。
单例模式的特点:
- 类构造器私有
- 持有自己类的引用
- 对外提供获取实例的静态方法
我们先用一个简单示例了解一下单例模式的用法。
public class SimpleSingleton {
//持有自己类的引用
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
//私有的构造方法
private SimpleSingleton() {
}
//对外提供获取实例的静态方法
public static SimpleSingleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
System.out.println(SimpleSingleton.getInstance().hashCode());
System.out.println(SimpleSingleton.getInstance().hashCode());
}
}
打印结果:
1639705018
1639705018
我们看到两次获取SimpleSingleton实例的hashCode是一样的,说明两次调用获取到的是同一个对象。
可能很多朋友平时工作当中都是这么用的,但我要说这段代码是有问题的,你会相信吗?
不信,我们一起往下看。
2 饿汉和懒汉模式
在介绍单例模式的时候,必须要先介绍它的两种非常著名的实现方式:饿汉模式 和 懒汉模式。
2.1 饿汉模式
实例在初始化的时候就已经建好了,不管你有没有用到,先建好了再说。具体代码如下:
public class SimpleSingleton {
//持有自己类的引用
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
//私有的构造方法
private SimpleSingleton() {
}
//对外提供获取实例的静态方法
public static SimpleSingleton getInstance() {
return INSTANCE;
}
}
饿汉模式,其实还有一个变种:
public class SimpleSingleton {
//持有自己类的引用
private static final SimpleSingleton INSTANCE;
static {
INSTANCE = new SimpleSingleton();
}
//私有的构造方法
private SimpleSingleton() {
}
//对外提供获取实例的静态方法
public static SimpleSingleton getInstance() {
return INSTANCE;
}
}
使用静态代码块的方式实例化INSTANCE对象。
使用饿汉模式的好处是:没有线程安全的问题,但带来的坏处也很明显。
private static final SimpleSingleton INSTANCE = new SimpleSingleton();
一开始就实例化对象了,如果实例化过程非常耗时,并且最后这个对象没有被使用,不是白白造成资源浪费吗?
这个时候你也许会想到,不用提前实例化对象,在真正使用的时候再实例化不就可以了?
这就是我接下来要介绍的:懒汉模式。
2.2 懒汉模式
顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。具体代码如下:
public class SimpleSingleton2 {
private static SimpleSingleton2 INSTANCE;
private SimpleSingleton2() {
}
public static SimpleSingleton2 getInstance() {
if (INSTANCE == null) {
INSTANCE = new SimpleSingleton2();
}
return INSTANCE;
}
}
示例中的INSTANCE对象一开始是空的,在调用getInstance方法才会真正实例化。
但这段代码还是有问题。
2.3 synchronized关键字
上面的代码有什么问题?
假如有多个线程中都调用了getInstance方法,那么都走到 if (INSTANCE == null) 判断时,可能同时成立,因为INSTANCE初始化时默认值是null。这样会导致多个线程中同时创建INSTANCE对象
,即INSTANCE对象被创建了多次,违背了只创建一个INSTANCE对象的初衷。
那么,要如何改进呢?
答:最简单的办法就是使用synchronized关键字。
改进后的代码如下:
public class SimpleSingleton3 {
private static SimpleSingleton3 INSTANCE;
private SimpleSingleton3() {
}
public synchronized static SimpleSingleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new SimpleSingleton3();
}
return INSTANCE;
}
public static void main(String[] args) {
System.out.println(SimpleSingleton3.getInstance().hashCode());
System.out.println(SimpleSingleton3.getInstance().hashCode());
}
}
在getInstance方法上加synchronized关键字,保证在并发的情况下,只有一个线程能创建INSTANCE对象的实例。
这样总可以了吧?
答:不好意思,还是有问题。
有什么问题?