1. 单例模式介绍
单例模式提供了一种创建对象的最佳方式。
由某类负责创建自己的对象,并提供该对象的访问方式,以确保该对象是该类唯一的实例。
(1) 适用情况:
存在一个全局使用的类被频繁的创建和销毁时,可以考虑使用单例模式。
(2) 优点:
在堆中有且仅有一个实例,避免了频繁创建销毁以及保存的内存开销。
(3) 缺点:
没有接口,不能继承。
它在类的内部创建了自己的对象,这与单一职责原则冲突,它本来不应该关心外部是怎么实例化它的。
2. 单例模式实例
(1) 懒汉式(线程不安全)
先来看一种最简单的单例模式:
public class Singleton {
private static Singleton instance;
// 私有构造方法
private Singleton() {
}
public static Singleton getSingleton() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
为什么叫懒汉,因为它不急着创建实例,而是等到判定为空的时候才去创建
它最大的问题就是线程不安全,因为可能会有多个线程判定instance == null,从而创建多个实例,严格意义上说,它并不能算单例模式。
(2) 懒汉式(线程安全)
上边的实现是线程不安全的,那给getSingleton方法增加synchronized不就行了吗:
public class Singleton {
private static Singleton instance;
// 私有构造方法
private Singleton() {
}
public static synchronized Singleton getSingleton() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这样做确实是可以保证线程安全的,但是加锁会影响效率
其实除了第一次需要创建之外,后续的加锁就没有意义了
(3) 饿汉式(常用)
饿汉式指的是在类加载的时候,就直接创建了实例:
public class Singleton {
private static Singleton instance = new Singleton();
// 私有构造方法
private Singleton() {
}
public static Singleton getSingleton() {
return instance;
}
}
这是比较常用的方式,也可以保证线程安全
但是在类加载的时候就实例化的话,万一它很消耗资源,就有点浪费内存
(4) 双重校验锁(面试专属)
使用双重校验锁的方式能同时保证线程安全和高性能:
public class Singleton {
// 注意这里的volatile
private volatile static Singleton instance;
// 私有构造方法
private Singleton() {
}
public static Singleton getSingleton() {
// 第一次判断是否为空,为空才创建,否则直接返回
if (instance == null) {
synchronized (Singleton.class) {
// 第二次判断是否为空,防止已被其他线程实例化之后重复实例化
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这种方式中判断了两次instance == null:
第一次判断是否为空,为空才创建,否则直接返回
第二次判断是否为空,防止已被其他线程实例化之后重复实例化
这里的关键是在声明instance变量的时候,增加了volatile关键字
volatile有两个作用:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。但如果对该变量执行的是非原子操作,它不能保证线程安全
- 禁止指令重排序
(5) 静态内部类(优化的饿汉式)
上边说过饿汉式在类加载的时候,就直接创建了实例,比较浪费内存
可以使用静态内部类的方式,当显示调用getInstance方法时,才会去装载SingletonHolder类,从而实例化instance
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 私有构造方法
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
(6) 枚举(用的少,大神推荐)
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
3. 一些思考
最近工作中遇到了一个问题,详细的暂时不展开说了
大致就是某一个系统在长时间运行后,出现了元空间Metaspace的OOM错误
通过阿里开源的内存监控工具Arthas发现了端倪:某一个类加载器被创建了几万次!
阅读代码发现,是因为在我们的代码里重复创建一个类的对象,而每次创建该对象时,都会创建一个类加载器,最终使得元空间OOM了。
后来,我们使用了单例模式,不再重复创建该对象,就完美地解决了这个问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。