使用场景
实际的开发中,为了避免创建多个对象消耗过多的资源,或者某个类的对象只能有一个,所以就需要使用单例模式来确保某个类只能对外提供一个对象。
特点
- 类的构造函数一般用private修饰,不对外公开
- 一般通过一个静态方法返回单例对象
- 必须保证线程安全,即在多线程场景下能确保只有一个单例对象
1 懒汉加载
1.1 简单粗暴有缺点
public class Singleton {
private Singleton() {}
private static Singleton single = null;
//缺点:每一次都要 synchronized 同步,造成不必要的开销
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
1.2 改进:双重检查锁定
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
第一重判断:改进了不用每次都同步的缺点 ,提升效率
第二重判断:如果两个线程都进入了第一重判断,由于同步,只能进来一个。A进入了创建了实例,出来后B还可以进入,如果没有第二次判断,就会生成多个instance
另外可能失效:java的指令重排序。single = new Singleton();可以分成多条汇编指令
(1)、给Singleton实例分配内存(2)、调用构造函数,初始化成员(3)、将instance对象指向分配内存的空间,注意此时instance就不为null了
可以是123,也可以是132,如果是132,A执行3的时候,2未执行,此时instance就不为空,B就把instance取走了,这就是失效的原因。
解决办法:private volatile static Singleton single = null
即添加volatile修饰符,这样就可以保证instance每次都从主内存读取,避免了上边的问题,但会略影响性能。这种单例模式也是在第一次执行getInstance()时创建单例,但第一次反映稍慢。
这种方式目前使用的较多。
1.3 推荐使用:静态内部类单例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
这种方式只有在的第一次调用getInstance()方法时,虚拟机才会加载SingletonHolder类,并初始化instance实例,即保证了线程同步,也能保证单例的唯一性,相对双重检查锁定单例模式简单了许多,推荐使用这种方式来实现单例模式。
2 饿汉单例模式
//饿汉式单例类.在类初始化时,已经自行实例化,以后不再改变,所以天生是线程安全的
public class Singleton {
private Singleton() {}
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
3 容器单例模式
public class SingletonManager {
private static Map<String, Object> instanceMap = new HashMap<>();
private SingletonManager() {
}
public static void addInstance(String key, Object instance) {
if (!instanceMap.containsKey(key)) {
instanceMap.put(key, instance);
}
}
public Object getInstance(String key) {
return instanceMap.get(key);
}
}
采用Map集合管理对象的实例,保证实例的唯一性,这种方式多用于管理多种类的实例场景,同时你的类并不一定需要实现单例机制,因为SingletonManager可以解决这个问题。你只需在初始化时创建对应类的实例并调用addInstance(String key, Object instance)
来进行保存,使用时调用getInstance(String key)
,即可根据key得到对应类的实例。
声明:此文章为本人学习笔记
如果您觉得有用,欢迎关注我的公众号,我会不定期发布自己的学习笔记、资料、以及感悟,欢迎留言,与大家一起探索AI之路。
AI探索之路
网友评论