高效Java编程-03. 使用私有构造方法或枚类实现Singleton属性

  单例是一个仅实例化一次的类[Gamma95]。单例对象通常表示无状态对象,如函数 (条目 24) 或一个本质上唯一的系统组件。让一个类成为单例会使测试它的客户变得困难,因为除非实现一个作为它类型的接口,否则不可能用一个模拟实现替代单例。

  有两种常见的方法来实现单例。两者都基于保持构造方法私有和导出公共静态成员以提供对唯一实例的访问。在第一种方法中,成员是 final 修饰的属性:

   // Singleton with public final field
   public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() 
    public void leaveTheBuilding() 
   }

  私有构造方法只调用一次,来初始化公共静态 final Elvis.INSTANCE 属性。缺少一个公共的或受保护的构造方法,保证了全局的唯一性:一旦 Elvis 类被初始化,一个 Elvis 的实例就会存在——不多也不少。客户端所做的任何事情都不能改变这一点,但需要注意的是:特权客户端可以使用 AccessibleObject.setAccessible 方法,以反射方式调用私有构造方法(详见第 65 条)。如果需要防御此攻击,请修改构造函数,使其在请求创建第二个实例时抛出异常。

  在第二个实现单例的方法中,公共成员是一个静态的工厂方法:

   // Singleton with static factory
   public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() 
    public static Elvis getInstance() 

    public void leaveTheBuilding() 
   }

  所有对 Elvis.getInstance 的调用都返回相同的对象引用,并且不会创建其他的 Elvis 实例(与前面提到的警告相同)。

  公共属性方法的主要优点是 API 明确表示该类是一个单例:公共静态属性是 final 的,所以它总是包含相同的对象引用。 第二个好处是它更简单。

  静态工厂方法的优势之一在于,它提供了灵活性:在不改变其 API 的前提下,我们可以改变该类是否应该为单例的想法。工厂方法返回该类的唯一实例,但是,它很容易被修改,比如,改为每个调用该方法的线程返回一个唯一的实例。 第二个好处是,如果你的应用程序需要它,可以编写一个泛型单例工厂(generic singleton factory )(详见第30 条)。 使用静态工厂的最后一个优点是,可以通过方法引用(method reference)作为提供者,例如 Elvis::instance 等同于 Supplier<Elvis>。 除非满足以上任意一种优势,否则还是优先考虑公有域(public-field)的方法。

  为了将上述方法中实现的单例类变成是可序列化的 (第 12 章),仅仅将 implements Serializable 添加到声明中是不够的。为了保证单例模式不被破坏,必须声明所有的实例字段为 transient,并提供一个 readResolve 方法(详见第 89 条)。否则,每当序列化的实例被反序列化时,就会创建一个新的实例,在我们的例子中,导致出现新的 Elvis 实例。为了防止这种情况发生,将如下的 readResolve 方法添加到 Elvis 类:

   // readResolve method to preserve singleton property
   private Object readResolve() {
    // Return the one true Elvis and let the garbage collector
    // take care of the Elvis impersonator.
    return INSTANCE;
   }

  实现一个单例的第三种方法是声明单一元素的枚举类:

   // Enum singleton - the preferred approach
   public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() 
   }

  这种方式类似于公共属性方法,但更简洁,无偿地提供了序列化机制,并提供了防止多个实例化的坚固保证,即使是在复杂的序列化或反射攻击的情况下。这种方法可能感觉有点不自然,但是 单一元素枚举类通常是实现单例的最佳方式 。注意,如果单例必须继承 Enum 以外的父类 (尽管可以声明一个 Enum 来实现接口),那么就不能使用这种方法。

文章列表

更多推荐

更多
  • Spark编程-结构化流式编程指南 概述,简单例子,编程模型,使用 Dataset 和 DataFrame 的API,连续处理,额外信息,基本概念,处理 Eventtime 和 Late Data,faulttolerance 语义,创建流式 DataFrame 和流式
  • Spark编程-20 Spark 配置Spark 属性,Environment Variables环境变量,Configuring Logging配置 Logging,Overriding configuration directory覆盖配置目录,Inhe
  • Spark编程-在Mesos上运行Spark 运行原理,安装 Mesos,连接 Spark 到 Mesos,Mesos 运行模式,Mesos Docker 支持,集成 Hadoop 运行,使用 Mesos 动态分配资源,配置,故障排查和调试,从源码安装,第三方软件包,验证,上传 S
  • Spark编程-Running Spark on YARN 启动 Spark on YARN,准备,配置,调试应用,在安全集群中运行,添加其他的 JARs,配置外部的 Shuffle Service,用 Apache Oozie 来运行应用程序,Kerberos 故障排查,使用 Spark Hi
  • Spark编程-Spark 调优 数据序列化,内存调优,其它考虑,,内存管理概论,确定内存消耗,优化数据结构,序列化 RDD 存储,GC优化,并行级别,Reduce任务内存使用,广播大变量,数据局部性, 由于大多数Spark计算都在内存中,所以集群中的任何资源(C
  • Spark编程-Spark Standalone模式 安装 Spark Standalone 集群,手动启动一个集群,集群启动脚本,提交应用程序到集群中,启动 Spark 应用程序,Resource Scheduling资源调度,监控和日志,与 Hadoop 集成,配置网络安全端口,高可用
  • Spark编程-Monitoring and Instrumentation Web 界面,Metrics,高级工具,事后查看,REST API,环境变量,Spark配置选项,API 版本控制策略, 有几种方法来监视 Spark 应用程序:Web UI,metrics 和外部工具。 Web 界面每
  • Spark编程-Spark提交任务Submitting Applications 打包应用依赖,用 sparksubmit 启动应用,Master URLs,从文件中加载配置,高级的依赖管理,更多信息, 在 script in Spark的 `bin` 目录中的`spark-submit` 脚本用与在集群上启动
  • Spark编程-作业调度 概述,跨应用调度,应用内调度,动态资源分配,公平调度资源池,资源池默认行为,配置资源池属性,配置和部署,资源分配策略,优雅的关闭Executor(执行器),概述Spark 有好几计算资源调度的方式。首先,回忆一下 [集群
  • Spark编程-Spark 概述 安全,下载,运行示例和 Shell,在集群上运行,进一步学习链接, Apache Spark 是一个快速的,通用的集群计算系统。它对 Java,Scala,Python 和 R 提供了的高层 API,并有一个经优化的支持通用执行图
  • 近期文章

    更多
    文章目录

      推荐作者

      更多