手机用户请
横屏
获取最佳阅读体验,REFERENCES
中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。
平台 | 地址 |
---|---|
CSDN | https://blog.csdn.net/sinat_28690417 |
简书 | https://www.jianshu.com/u/3032cc862300 |
个人博客 | https://yiyuery.github.io/NoteBooks/ |
单例模式:确保一个类只有一个实例,并提供一个全局访问点
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
public static synchronized Singleton getInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
public class StaticSingleton {
private static StaticSingleton instance = new StaticSingleton();
private StaticSingleton() {
}
public static StaticSingleton getInstance() {
return instance;
}
}
public class DoubleLockSingleton {
private volatile static DoubleLockSingleton instance;
private DoubleLockSingleton() {
}
public static synchronized DoubleLockSingleton getInstance() {
//检查单例是否已存在
if (instance == null) {
//多线程进入获取初始化实例时
synchronized (DoubleLockSingleton.class) {
//进入区块后,再检查一次,如果还为null,才创建实例
if (instance == null) {
instance = new DoubleLockSingleton();
}
}
}
return instance;
}
}
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟 使用静态成员变量创建单例
方式不同的是:后者只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。
因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比 使用静态成员变量创建单例
方式就显得很合理(这也是延迟初始化的优势所在)。
public class StaticHolderSingleton {
private static class SingletonHolder {
private static final StaticHolderSingleton INSTANCE = new StaticHolderSingleton();
}
private StaticHolderSingleton (){}
public static StaticHolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式可以组合前文的单例创建方式来提供一个单例的管理入口。
@Getter
public enum SingletonEnum {
/**
* 单例
*/
INSTANCE(Singleton.getInstance());
private Singleton singleton;
SingletonEnum(Singleton singleton) {
this.singleton = singleton;
}
}
或是这样
@Getter
public enum SingletonEnum {
/**
* 单例
*/
INSTANCE;
public void method(){
//TODO STH
}
}
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
而且不能通过 reflection attack 来调用私有构造方法。
上述实现方式总结如下:
方式 | 昵称 | 线程安全 | 延迟初始化 | 特点 |
---|---|---|---|---|
经典实现方式 | 懒汉式,线程不安全 | 否 | 是 | 不支持多线程 |
synchronized 关键词 | 懒汉式,线程安全 | 是 | 是 | 多线程性能不佳 |
使用静态成员变量创建单例 | 饿汉式 | 是 | 否 | 对于项目规模大,单例多的时候谨慎使用 |
双重检查加锁 | 懒汉式 | 是 | 是 | 多线程性能良好 |
登记式/静态内部类 | \ | 是 | 是 | 多线程性能良好,但是只支持静态域 |
枚举方式 | \ | \ | \ | 简洁 |
单例模式特点
设计原则
对于公共使用的,全局唯一的访问点,可以使用单例封装内部实现逻辑后对外提供能力,而非多处创建依赖对象,消耗系统资源。