单例模式(Singleton Pattern)是一个比较简单的模式,其定义如下:
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.)
方法:使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化的(在Singleton中自己使用new Singleton())。
确保一个类最多只有一个实例,并提供一个全局访问点.
1 饿汉模式
public class PreloadSingleton {
public static PreloadSingleton instance = new PreloadSingleton();
//其他的类无法实例化单例类的对象
private PreloadSingleton() {
};
public static PreloadSingleton getInstance() {
return instance;
}
}
缺点:很明显,没有使用该单例对象,该对象就被加载到了内存,会造成内存的浪费。
优点:线程安全
2 懒汉模式
第一版本
public class Singleton {
private static Singleton instance=null;
private Singleton(){
};
public static Singleton getInstance()
{
if(instance==null)
{
instance=new Singleton();
}
return instance;
}
}
线程不安全
原因:
懒加载不浪费内存,但是无法保证线程的安全。首先,if判断以及其内存执行代码是非原子性的。其次,new Singleton()无法保证执行的顺序性。
不满足原子性或者顺序性,线程肯定是不安全的,这是基本的常识,不再赘述。我主要讲一下为什么new Singleton()无法保证顺序性。我们知道创建一个对象分三步:
memory=allocate();//1:初始化内存空间
ctorInstance(memory);//2:初始化对象
instance=memory();//3:设置instance指向刚分配的内存地址
jvm为了提高程序执行性能,会对没有依赖关系的代码进行重排序,上面2和3行代码可能被重新排序。我们用两个线程来说明线程是不安全的。线程A和线程B都创建对象。其中,A2和A3的重排序,将导致线程B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象(线程不安全)。

为了线程安全使用关键字 synchronized
第二版
public class Singleton {
private static Singleton instance = null;
private Singleton() {
};
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
synchronized加载getInstace()函数上确实保证了线程的安全。但是,如果要经常的调用getInstance()方法,不管有没有初始化实例,都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间,如果对象已经实例化了,我们没有必要再使用synchronized加锁,直接返回对象。
3 双重校验锁
第三版
public class Singleton {
private static Singleton instance = null;
private Singleton() {
};
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
4 静态内部类
public class Singleton {
private static final Singleton INSTANCE = new instance();
private Singleton() {
};
public static final Singleton getInstance() {
return Singleton.INSTANCE;
}
}
静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
5 枚举
public enum Singleton {
INSTANCE;
public void method(){
}
}
枚举的方式是比较少见的一种实现方式,但是看上面的代码实现,却更简洁清晰。并且她还自动支持序列化机制,绝对防止多次实例化。
6 总结
一般情况下,懒汉式(包含线程安全和线程不安全两种方式)都比较少用;饿汉式和双检锁都可以使用,可根据具体情况自主选择;在要明确实现 lazy loading 效果时,可以考虑静态内部类的实现方式;若涉及到反序列化创建对象时,大家也可以尝试使用枚举方式。
参考博客:
https://blog.youkuaiyun.com/A1342772/article/details/91349142
https://blog.youkuaiyun.com/absolute_chen/article/details/93380566
https://refactoringguru.cn/design-patterns/singleton/java/example
本文主要用来自学使用,如有侵权,请联系我,速删,谢谢!
本文详细介绍了单例模式的六种实现方法,包括饿汉模式、懒汉模式的不同版本、双重校验锁、静态内部类、枚举等,并对比了它们之间的优缺点。

被折叠的 条评论
为什么被折叠?



