单例模式

本文深入解析单例模式的概念,探讨其在确保系统中单一实例的重要性,特别是在资源管理和配置信息读取方面。文章详细介绍了单例模式的多种实现方式,包括饿汉式、懒汉式、双重检查锁定和静态内部类等,每种方式的特点和适用场景,以及如何通过枚举实现单例。
摘要由CSDN通过智能技术生成

什么是单利模式

单例模式,顾名思义就是程序在运行的过程中,有且只有一个实例。

为什么需要它

有些对象我们只需要一个,像是线程池,缓冲等,这类对象只能有一个实例,一旦产生多个实例就会出现问题。所以,我们必须找到一种方法来确保我们的代码中只有一个实例。

许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。


单例模式的定义

确保这个类在内存中只会存在一个对象,而且自行实例化并向整个应用系统提供这个实例。

单例模式的应用场景

一般创建一个对象需要消耗过多的资源,如:访问I0和数据库等资源或者有很多个地方都用到了这个实例。

单例模式的几种基本写法:

饿汉式
public class Singleton {
    // 将构造函数私有化,这样就无法通过new 创建对象了
    private Singleton(){}
    // 静态化,在类加载的时候就将对象创建好
    private static final Singleton INSTANCE=new Singleton();
    public static Singleton getInstance(){
        return INSTANCE;
    }
}

   饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

优点:简单、线程安全。

缺点:不需要的时候就加载了,造成资源浪费。

懒汉式
public class Singleton{  
      // 将构造函数私有化,这样就无法通过new 创建对象了
      private Singleton(){}  
      // 将类实例设置为null,在需要的时候加载,节省资源
      private static Singleton instance = null;  
      public static Singleton newInstance(){  
          if(null == instance){  
              instance = new Singleton();  
          }  
          return instance;  
      }  
}

懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。但是这里的懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题,实现如下。

懒汉式适用场景

如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择

缺点: 效率低,第一次加载需要实例化,反应稍慢。每次调用getInstance方法都会进行同步,消耗不必要的资源。

  上边的两种是最常见的,顾名思义懒汉式和饿汉式,一个是拿时间换空间,一个是拿空间换时间,懒汉式只有我需要他的时候才去加载它,懒加载机制,饿汉式不管需不需要我先加载了再说,先在内存中开辟一块空间,占用一块地方,等用到了直接就拿来用.这两种是最基本的单例模式。

双重检查单例(DCL实现单例)
  public class Singleton {
      private Singleton(){}
      private volatile static Singleton instance;
      public static Singleton getInstance(){
          if(instance == null){
          	  // 为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized同步锁,锁住整个类(注意,这里不能使用对象锁)
              synchronized (Singleton.class){
                  // 进入Synchronized 临界区以后,还要再做一次判空。
                  // 因为当两个线程同时访问的时候,线程A构建完对象,
                  // 线程B也已经通过了最初的判空验证,不做第二次判空的话,
                  // 线程B还是会再次构建instance对象
                  if(instance == null){
                      instance = new Singleton();
                  }
              }
          }
          return instance;
      }
  }

优点:线程安全;延迟加载;效率较高

缺点:第一次加载时会稍慢,jdk1.5之之前有可能会加载会失败

这种写法估计是我们在开发中最常用的,这次代码的亮点是是在getInstance()方法中进行了双重的判断,第一层判断的主要避免了不必要的同步,第二层判断是为了在null的情况下再去创建实例;举个简单的列子:假如现在有多个线程同时触发这个方法: 线程A执行到nstance = new Singleton(),它大致的做了三件事:

(1)、给Singleton实例分配内存,将函数压栈,并且申明变量类型。
(2)、初始化构造函数以及里面的字段,在堆内存开辟空间。
(3)、将instance对象指向分配的内存空间。

这种写法也并不是保证完全100%的可靠,由于java编译器允许执行无序,并且jdk1.5之前的jvm(java内存模型)中的Cache,寄存器到主内存的回写顺序规定,第二个和第三个执行是无法保证按顺序执行的,也就是说有可能1-2-3也有可能是1-3-2; 这时假如有A和B两条线程,A线程执行到3的步骤,但是未执行2,这时候B线程来了抢了权限,直接取走instance这时候就有可能报错。

静态内部内实现单例
public class Singleton {
      private volatile static Singleton instance;
      private Singleton() {
      }
      public static class SingletonInstance {
          private static final Singleton INSTANCE = new Singleton();
      }
      public static Singleton getInstance() {
          return SingletonInstance.INSTANCE;
      }
  }

利用了类加载机制来保证只创建一个instance实例。它与饿汉模式一样,也是利用了类加载机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。

这种方式不仅确保了线程的安全性,也能够保证对象的唯一性,同时也是延迟加载,很多技术大牛也是这样推荐书写。

枚举实现单例
// 有了enum,JVM会阻止反射获取枚举的私有构造方法。
public enum SingletonEnum {
      INSTANCE;
      public SingletonEnum getSingleton() {
      	return INSTANCE; 
      }
}

优点:相对于其他单例来说枚举写法最简单,并且任何情况下都是单例的,JDK1.5之后才有的。

作者:安仔夏天勤奋
链接:https://www.jianshu.com/p/12d1a151982e
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值