单例模式

单例模式是什么?

单例模式:一个类只有一个实例。

适用场景

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值