单例模式(三)

一、定义

  确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

二、构建方式

通常单例模式在Java语言中,有两种构建方式:

  • 懒汉方式。指全局的单例实例在第一次被使用时构建。
  • 饿汉方式。指全局的单例实例在类装载时构建。

实现要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
对应着以下几点:

  • 单例模式的类只提供私有的构造函数
  • 类定义中含有一个该类的静态私有对象
  • 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象

1. 饿汉式

public class Krryblog {

  // 1.将构造函数私有化,不可以通过 new 的方式来创建对象
  private Krryblog() {}

  // 2.在类的内部创建自行实例
  private static final Krryblog krryblog = new Krryblog();

  // 3.提供获取唯一实例的方法
  public static Krryblog getKrryblog() {
      return krryblog;
  }
}
View Code

缺点:一上来就创建对象了,如果该实例从始至终都没被使用过,则会造成内存浪费

2. 懒汉式

用到的时候再创建。

(1) 方法加锁

public class Krryblog {

  // 1. 将构造函数私有化,不可以通过new的方式来创建对象
  private Krryblog(){}

  // 2. 先不创建对象,等用到的时候再创建
  private static Krryblog krryblog = null;

  // 3. 调用到这个方法了,证明是要被用到的了
  public static synchronized Krryblog getKrryblog() {

      // 4. 如果这个对象引用为null,我们就创建并返回出去,不为 null,就不再创建
      if (krryblog == null) {
          krryblog = new Krryblog();
      }

      return krryblog;
  }
}
View Code

注意,上面调用 getKrryblog 如果不加锁 synchronized,在多线程环境下无效,所以多线程下一定要加锁 synchronized。

(2) 双重检测机制(DCL)

直接在方法上加锁的方式其实不够好,因为在方法上加了内置锁在多线程环境下性能会比较低下,所以我们可以将锁的范围缩小。

public class Krryblog {
  private Krryblog() {}

  // 可能会有重排序问题,加上 volatile 关键字解决
  // volatile 有内存屏障的功能
  private static volatile Krryblog krryblog = null;

  public static Krryblog getKrryblog() {
    // 这里判断是否为空,主要是为了提高性能
    if (krryblog == null) {

      // 锁住这里就行了,里面还有一个判断是否为空
      // 将锁的范围缩小,提高性能
      synchronized (Krryblog.class) {

        // 再判断一次是否为null
        // 如果只在外面加 null 判断,若两个线程同时调用 getKrryblog,同时判断外面的 null,那也会创建出两个对象
        // 所以在锁的里面也要加上 null 判断
        if (krryblog == null) {
          krryblog = new Krryblog();
        }
      }
    }
    return krryblog;
  }
}
View Code

(3) 静态内部类(推荐)

原理:当任何一个线程第一次调用 getInstance()时,都会使 SingletonHolder 被加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。(被调用时才进行初始化)。初始化静态数据时,Java 提供了的线程安全性保证(所以不需要任何的同步)

public class Krryblog {

  private Krryblog() {}

  // 使用内部类的方式来实现懒加载
  private static class LazyHolder {
    // 创建单例对象
    private static final Krryblog INSTANCE = new Krryblog();
  }

  // 获取对象
  public static final Krryblog getInstance() {
    return LazyHolder.INSTANCE;
  }
  
}
View Code

三、应用场景

  • 要求生成唯一序列号的环境;
  • 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
  •  创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
原文地址:https://www.cnblogs.com/myitnews/p/11425320.html