单例模式是什么?
单例模式:一个类只有一个实例。
适用场景
1.产生对象会消耗过多的资源,避免频繁的创建与销毁对象,造成资源的浪费,就将这个对象设为单例对象。
如:对数据库的操作,访问IO,数据库连接池,线程池(threadpool), 网络请求。
2. 这个对象有且只应该有一个,如果这个对象有多个,可能会造成结果不一致,资源过量使用,
如:一个系统只能有:一个窗口管理器,文件系统,计数工具。ID(序号)生成器,缓存,日志,注册表,日志对象。
3. 只想产生一个单例
Spring中的bean默认是单例,
优缺点
优点:减少系统内存开支,避免对资源的多重操作,同时操作。
缺点:扩展困难,容易引发内存泄漏,测试困难。
使用注意点
如果一个类初始化需要消耗很多时间,或经常用到该单例,就饿汉。
如果资源占用的内存较多,或者这个类不定会用到,就懒加载。
对于不是频繁的创建和销毁,且小环也不会消耗太多资源的情况,可以不用单例模式。
实现方式
1.懒汉式(加Synchronized): 要用到该类的单例对象时,再去加载,多线程环境下线程不安全,加Synchronized。
优点: 用到单例对象才去加载,不会造成资源的浪费;
缺点: 线程不安全,多个线程判断均为null, 实例化多个对象,且指令重排会返回log对象;加Synchronized在方法上又太笨重。
2.双重锁校验:volatile和Synchronized关键字 ,双重null: 一重null只有实例化对象才加锁,获得对象时不加锁;二重null确保在为null下才实例化对象 volatile:禁止指令重排,避免产生空对象(1.为对象分配内存空间;2.初始化对象;3.将引用指向内存空间)
优点: 用到单例对象才去加载,不会造成资源的浪费;只有在实例化对象时才加锁,提高效率。
缺点: JDK 版本小于 1.5 时会有 DCL 失效的问题
3.饿汉式:类加载时,就生成单例对象。
优点: 简单,线程安全。
缺点: 每个类都会实例化单例对象,有的类不需要单例对象造成浪费。
4.静态内部类: Singleton类里面还有一个静态内部类SingletonHolder,在静态内部类里实例化Singleton对象,由于这种内部类与外部类没有从属关系,外部类加载时,不会加载静态内部类,实现了懒加载;由于实例化对象在静态类里面,所以在实例化对象是通过JVM类加载来完成的,线程安全。
优点: 线程安全,又是懒加载
缺点:
5.枚举
优点: 既能同步,又能防止反序列化重新创建新的对象。
参考:https://zhuanlan.zhihu.com/p/25733866
代码:
1.懒汉式(加Synchronized): 要用到该类的单例对象时,再去加载,多线程环境下线程不安全,加Synchronized。
优点: 用到单例对象才去加载,不会造成资源的浪费;
缺点: 线程不安全,多个线程判断均为null, 实例化多个对象,且指令重排会返回lnull对象;加Synchronized在方法上又太笨重。
// 问题1: 创建多个singleton
// 多个线程判断为null, 由于singleton没有被volatile修饰,
// 所以当一个线程已经new 了一个singleton, 其他线程不可见,
// 获得锁后,依然会new Singleton(); 创建多个singleton
// 问题2: 指令重排
// singleton = new Singleton();
// 三步: 分配内存空间; init初始化; singleton指向内存空间
// 指令重排 分配内存空间; singleton指向内存空间;init初始化
// 造成singleton != null, 但是没有初始化
// 所以会创建,没有初始化的单例对象
// 加在方法上又太笨重,获取和创建singleton都要阻塞等待。
public class LazyMan {
private LazyMan() {
};
private static LazyMan singleton;
public static LazyMan getInstance() {
if(singleton == null) {
synchronized (LazyMan.class) {
singleton = new LazyMan();
}
}
return singleton;
}
}
50个线程打印得到的单例对象:
2.双重锁校验:
双重校验null:
第一重:提高效率,获取线程不需要加锁,只有创建线程的时候加锁。
第二重:以前singleton没有加volatile时,多个线程进入代码,判断singleton为null, 其中一个线程获取锁,new单例对象,退出程序,释放锁。由于singleton对其他线程不可见,所以线程依然认为singleton为null, 又创建了一个单例对象。这里已经有了volatile,本不需要,安全起见,增加了null判断。
volatile: 禁止指令重排,因为已经有了第二层判断null, 所以这里的volatile主要作用是禁止指令重排,避免产生空对象(1.为对象分配内存空间;2.初始化对象;3.将引用指向内存空间)
优点: 用到单例对象才去加载,不会造成资源的浪费;只有在实例化对象时才加锁,提高效率。
缺点: JDK 版本小于 1.5 时会有 DCL 失效的问题
public class Hungry {
private Hungry() {
}
private static final Hungry singleton = new Hungry();
public stati