前言
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
一、标准饿汉式单例
在类加载的时候就创建单例对象,后续就只需要调用即可。
优点:
1.线程安全:无论如何多线程访问,保证唯一实例
2.调用、反应速度快:因为类加载时就已经创建,可以不加判断地直接调用。
缺点:
1.资源利用率低:有可能创建之后一直没有被调用,导致资源浪费。
2.加载速度慢:因为类加载时就创建单例对象,会影响加载速度。
0.原初饿汉式
class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
1.静态代码块
适合复杂的实例化,例如实例化类时加载一些外部的文件等
class Singleton {
private static final Singleton instance;
private Singleton() {}
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
二、标准懒汉式单例
调用实例时再加载。
由于其在类加载时并不自行实例化,这种技术又被称为延迟加载(Lazy Load),即需要的时候再加载实例。
优缺点:自行对比饿汉式。
0.原初懒汉式
刚听说这个概念的同学可能不假思索地写下如下代码。
此时该方法在单线程情况下已经足够了,但并不满足多线程安全。
class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
1.方法同步(基本不用)
此时这份代码具有线程安全性,但由于是对方法上锁,所以多线程情况,速度会较慢。
class Singleton {
private static Singleton instance = null;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.双重检查锁定(面试重点)
那么基于锁方法这种解决方式太慢,自然而然大佬们就想着优化
如下:
class Singleton {
private static volatile Singleton instance = null;
private Singleton() { }
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
那么这里必须解决几个问题:
1.为啥双重选择?
如果只保留上面的if,将导致锁了跟没锁一样,当两个线程同时通过instance==null后,在锁那里等待,然后同样执行了两次new。即一个线程new,new后由于另一个线程已经有new的资格了,跟在后面new。
if (instance == null) {
synchronized (Singleton.class){
instance = new Singleton();
}
}
2.为啥volatile?
instance = new Singleton();
对于new而言,JVM可能自动做优化,其预期的执行顺序是:
1、为 instance 分配内存空间
2、初始化 instance
3、将 instance 指向分配的内存地址
JVM优化后可能导致变成1-》3-》2,此时,可能导致instance 还没有初始化完成,就已经被另一个线程调用发现非空,从而return了。即一个线程还没new完,另一个线程在return。
而volatile就可以阻止这种优化,当然不可避免的阻止了一些其他相关优化,因此可能导致效率上的降低。
三、IoDH
大佬们不会停下前进的步伐,他们发现有很多相似的需求,于是对底层进行了优化。
直接使用静态内部类提供支持,既满足了延迟加载,又通过JVM机制提供了对线程安全的支持。
class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
四、枚举(最佳实践)
除了new以外,克隆、反射和反序列化都能产生新对象,其中有些会对以上三种实现方式产生破坏,于是大佬们想出了第四种,枚举。
利用JVM机制,保证了单例类不会被反射,并且构造函数只被执行一次。
class Singleton {
private Singleton(){
}
public static enum SingletonEnum {
SINGLETON;
private Singleton instance = null;
private SingletonEnum(){
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}
}
}
优缺点
优点:
(1) 单例模式提供了对唯一实例的受控访问,可以严格控制客户如何以及何时访问它。
(2) 由于只提供一个对象,可以节约系统资源,避免了频繁创建删除,可以提高系统的性能。
(3) 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。
缺点:
(1) 由于单例模式中没有抽象层,拓展困难。
(2) 单例类的职责过重,既负责创建又负责具体功能。(违反单一职责原则)
(3) 如果实例化的共享对象长时间不被利用,可能会被自动垃圾回收机制回收,导致共享的单例对象状态的丢失。
总结
总得来看根据是否在类加载时直接加载分为饿汉式和懒汉式,其中饿汉式面试官一般懒得问,重点掌握懒汉式中的双重选择锁,如果只问怎么创建的话,当然还是给他一个枚举