Syong : 单例模式

本文探讨了单例模式的实现方式,包括饿汉模式、懒汉模式和双重校验锁(DCL)模式。饿汉模式在类加载时创建单例,线程安全但存在资源浪费。懒汉模式延迟加载,但在多线程环境下可能存在安全隐患。DCL模式通过双重检查确保线程安全,同时减少了不必要的同步。此外,还提到了使用静态内部类实现单例的线程安全和延迟加载特性。单例模式常用于网站访问人数统计、配置文件对象、缓存字典等场景。
摘要由CSDN通过智能技术生成

单例模式

What
Ensure a class has only one instance, and provide a global point of access to it.
确保类只有一个实例,并提供对它的全局访问点。

  1. 只有一个实例
  2. 提供全局访问点

How

  1. 饿汉模式
public class Singleton {
   //全局只有一个实例
   private static Singleton SINGLETON = new Singleton();
   //私有构造函数
   private Singleton() {};
   //提供全局访问点(方法)
   public static Singleton getSingleton() {
       return SINGLETON;
   }
}

饿汉模式在类Singleton加载到jvm初始化的时候,就为static字段的SINGLETON创建了对象,由于一个类在整个生命周期中只会被加载一次,此时SINGLETON就是全局里唯一一个Singleton的实例,所以饿汉模式天生线程安全

  1. 懒汉模式
public class Singleton {
   //全局只有一个实例
   private static Singleton SINGLETON = null;
   //私有构造函数
   private Singleton(){};
   //提供全局访问点(方法)
   public static Singleton getSingleton(){
       if ( null == SINGLETON ) {
           SINGLETON = new Singleton();
       }
       return SINGLETON;
   }
}

懒汉模式在第一次调用该类时,才加载实例,表现为延时加载。然而线程不安全,现象一下同时有10个线程访问到SINGLETON = new Singleton();这句代码时,就会创建10个对象,不符合单例的概念。

到这里可以发现,给方法getSingleton()加锁,是否可以实现线程安全?
上代码(主要区别时getSingleton()这个方法是否有加锁)

public class Singleton {
    //全局只有一个实例
    private static Singleton SINGLETON = null;
    //私有构造函数
    private Singleton(){};
    //提供全局访问点(方法)
    public static synchronized Singleton getSingleton(){
        if ( null == SINGLETON ) {
            SINGLETON = new Singleton();
        }
        return SINGLETON;
    }
}

static synchronized 相当于 synchronized(Singleton.class),是一个类锁,所以一次只能有一个线程能调用getSingleton()方法。

这里我们又发现一个问题,性能太低,每次访问getSingleton()方法,都只能运行一个线程访问,但其实我们只有再第一次创建SINGLETON对象实例时,才需要加锁。怎么解决呢?这里就介绍到双重校验锁(Double Check Lock)。

  1. 双重校验锁模式(Double Check Lock,简称:DCL)
public class Singleton {
    //全局只有一个实例
    private static Singleton SINGLETON = null;
    //私有构造函数
    private Singleton(){};
    //提供全局访问点(方法)
    public static Singleton getSingleton(){
        if ( null == SINGLETON ) {
           synchronized (Singleton.class) {
               if ( null == SINGLETON ) {
                   SINGLETON = new Singleton();
               }
           }
        }
        return SINGLETON;
    }
}

可以看到,在每次同步之前,都会先判断SINGLETON是否已经创建,这样做的优点是:当SINGLETON已创建的时候,这个时候访问getSingleton()的时候,就无需再加锁,提升了性能。

DCL初步保证了懒加载的线程安全,但还是不完美,为什么呢?
因为同步块里的这句代码SINGLETON = new Singleton();,SINGELETON可能会异常。
引起这个问题的原因是:jvm的指令重排序.
解决方案:使用java的关键字volatile解决指令重排序,private static Singleton SINGLETON = null;改为private volatile static Singleton SINGLETON = null;

怎么去理解这个问题呢?

jvm执行这句代码的逻辑步骤大概为:
1:在堆里开辟内存空间,准备开始实例化;
2:实例化Singleton;
3:SINGLETON指向堆里已实例化的Singleton;

现在假设有两条线程(线程A与线程B),线程A运行到SINGLETON = new Singleton();代码段时,由于jvm的指令重排序,可能先执行步骤1与步骤3,此时如果线程B运行到if ( null == SINGLETON )时,会判断false返回一个未完全实例化SINGLETON对象,程序对其操作就会出现异常。

小小总结:

到这里已经介绍了饿汉模式与懒汉模式,饿汉模式是天生线程安全,也介绍了懒汉模式如何实现线程安全,接下来再介绍一种比较有趣实现方案。

  1. 静态内部类
public class Singleton {
    //私有构造函数
    private Singleton(){};
    //提供全局访问点(方法)
    public static Singleton getSingleton(){
        return SingletonHolder.SINGLETON;
    }
 
    //私有内部静态类
    private static class SingletonHolder {
        //全局只有一个实例
        private static Singleton SINGLETON = new Singleton();
    }
}

有许多大佬给出很详细的解释了,各位可以自行去搜索,毕竟会搜索也是一种能力

这里给我自己的理解:
1:可以看到内部静态类private static class SingletonHolder{}里的private static Singleton SINGLETON = new Singleton();,类似饿汉模式,一旦内部静态类被加载,那么SINGLETON也会被创建,jvm会保证一个类只会初始化一次,也就是说,SINGLETON只会被实例化一次(因为SingletonHolder初始化的时候,被关键字static修饰的字段也会相应被创建)。
2:那么内部静态类SingletonHolder什么时候被加载初始化呢?就是在getSingleton()方法被调用的时候,执行return SingletonHolder.SINGLETON;代码的时候。
3:有朋友会问,不是加载类Singleton的时候,就同时加载SingletonHolder内部静态类类吗?答案是:不会。不管是内部静态类还是内部非静态类,都不会随Singleton类的加载而加载

When & Where
用在哪里?
举例:

  1. 网站访问人数;
  2. 配置文件对象;
  3. 缓存字典对象;

还有例如日志文件,回收站,打印机,连接池,线程池等等。

结束语 : 优秀是一种习惯

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值