单例模式

使用场景

实际的开发中,为了避免创建多个对象消耗过多的资源,或者某个类的对象只能有一个,所以就需要使用单例模式来确保某个类只能对外提供一个对象。

特点

  • 类的构造函数一般用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 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探索之路

你可能感兴趣的:(单例模式)