一、什么是Singleton?

《设计模式》的作者、Eclipse和 Junit 的开发者 Erich Gamma 在它的理论体系中将 Singleton 定义为仅仅被实例化一次的类。在当今面向对象程序的实际开发中,Singleton 通常被用来代表一个无状态的对象,例如函数和那些本质上唯一的系统组件。

值得注意的是,使类成为 Singleton 会使得它的客户端测试变得非常困难,因为我们不可能给Singleton替换模拟实现,除非我们实现一个充当其类型的接口。

实现 Singleton 有三种常见方法,他们或是保持构造器私有并导出公有的静态成员,或是声明一个包含单个元素的枚举类型。

二、Singleton实现 —— 构造器私有

1、公有静态成员为一个final域

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

在这个类中,我们仅仅拥有一个私有的构造器,它也只在初始化final域时被调用一次。由于缺少可以使用的构造器,后续的程序无法再创建 Elvis 对象。这保证了在该Java程序的整个生命周期中, Elvis 对象有且只有一个存在。

但需要注意的是,一些高权限的客户端可以借助 AccessibleObject.setAccessible 方法通过反射机制调用私有的构造器。为了避免这样的可能的攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。

公有域方法的主要优势在于,API很清楚地表明了这个类是一个 Singleton ,毕竟这是一个公有的静态属性。另外,这个方法要更加简单。

2、公有静态成员为一个静态工厂方法

//Singleton with static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
pritvate Elvis() { ... }
public static Elvis getInstance(){ return INSTANCE; }
public void leaveTheBuilding(){ ... }
}

显然,无论怎样调用 getInstance 方法,返回的都是同一个对象的引用。注意上面提示的反射攻击问题依然存在。

静态工厂方法有两大优势

  • 第一,它提供了更多的灵活性,在不改变API的前提下,我们可以轻易地自由调整这个类是否是Singleton。工厂方法返回该类的唯一实例,但它很容易修改成别的样子,例如为每个调用该方法的线程提供唯一实例。
  • 第二,如果程序需要,我们可以编写一个泛型 Singleton 工厂。
  • 第三,我们可以通过方法引用作为提供者,比如 Elvis::instance 就是一个 Supplier< Elvis >

(注:方法引用是Java8的一个新特性)

除非我们需要上述的其中一种优势,我们还是应该选择更简单易懂的使用公有域的方法。

3、将利用上述方法实现的Singleton类变为可序列化的

使用上述两种方法实现的 Singleton ,要把他们变成可序列化的,不能仅仅在声明中加上 implements Serializable 。为了维护并保证 Singleton ,我们必须生命所有实例域都是瞬时的,并提供一个 readResolve 方法。否则在我们每次序列化时都会创建一个新的实例。为了防止这种情况,我们要在 Elvis 类中加入如下这样的 readResolve 方法。

//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;
}

三、Singleton实现 —— 声明包含单个元素的枚举类型

//Enum singleton - the preferred approach
public enum Elvis{
INSTANCE;
public void leaveTheBuilding(){ ... }
}

这种方法在功能上与公有域方法相似,但更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。 虽然这种方法还没有广 泛采用,但是单元素的枚举类型经常成为实现 Singleton 的最佳方法。 注意,如果 Singleton 必须扩展一个超类,而不是扩展 Enum 的时候,则不宜使用这个方法(虽然可以声明枚举去实现接口)。

Java中Singleton的三种实现方式解析的更多相关文章

  1. java中线程的三种实现方式

    一下记录下线程的3中实现方式:Thread,Runnable,Callable 不需要返回值时,建议使用Runnable:有返回值时建议使用Callable 代码如下所示: package com.f ...

  2. java中 this 的三种用法

    Java中this的三种用法 调用属性 (1)this可以调用本类中的任何成员变量 调用方法(可省略) (2)this调用本类中的成员方法(在main方法里面没有办法通过this调用) 调用构造方法 ...

  3. JAVA中单例模式的几种实现方式

    1 线程不安全的实现方法 首先介绍java中最基本的单例模式实现方式,我们可以在一些初级的java书中看到.这种实现方法不是线程安全的,所以在项目实践中如果涉及到线程安全就不会使用这种方式.但是如果不 ...

  4. 基于Java的二叉树的三种遍历方式的递归与非递归实现

    二叉树的遍历方式包括前序遍历.中序遍历和后序遍历,其实现方式包括递归实现和非递归实现. 前序遍历:根节点 | 左子树 | 右子树 中序遍历:左子树 | 根节点 | 右子树 后序遍历:左子树 | 右子树 ...

  5. c++中new的三种用法详细解析

    转载至: http://www.jb51.net/article/41524.htm 以下的是对c++中new的三种使用方法进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助. 一. ...

  6. Java中String对象两种赋值方式的区别

    本文修改于:https://www.zhihu.com/question/29884421/answer/113785601 前言:在java中,String有两种赋值方式,第一种是通过“字面量”赋值 ...

  7. 细说java中Map的两种迭代方式

    曾经对java中迭代方式总是迷迷糊糊的,今天总算弄懂了.特意的总结了一下.基本是算是理解透彻了. 1.再说Map之前先说下Iterator: Iterator主要用于遍历(即迭代訪问)Collecti ...

  8. Java创建线程的三种主要方式

    Java创建线程的主要方式 一.继承Thread类创建 通过继承Thread并且重写其run(),run方法中即线程执行任务.创建后的子类通过调用 start() 方法即可执行线程方法. 通过继承Th ...

  9. Java中对象的三种状态

    Java中的对象的三种状态是和垃圾回收紧密相关的,因此有必要深究. 状态一:可触及态:从根节点开始,可以搜索到这个对象,也就是可以访问到这个对象,也有人将其称为可达状态. 状态二:可复活态:从根节点开 ...

随机推荐

  1. Mysql 5.5升级5.8

    前言,因为升级跳板机,需要将mariadb 升级到10.2,也就是对应MySQL的5.8,废话不多说下面开始进行mariadb 5.5 的升级 Welcome to the MariaDB monit ...

  2. Spark-1.6.1 Hadoop-2.6.4 VMware Ubuntu 分布式集群搭建 全过程

    本文从头开始零基础完全配置,适合小白. 本文在vmware中配置三台虚拟机,一台做Master,两台Worker,hadoop 和spark只需要在Master上配置,然后cp到worker上,包括配 ...

  3. sudo 配置

    在ubuntu中由于禁用了root用户,默认情况下会把安装系统时建立的用户添加到sudoers中. 但在redhat和centos中并没有把任何root用户之外的用户默认的添加到sudoers之中.这 ...

  4. idea中类注释和方法注释的设置

    类注释设置 近几年版本的idea在设置类名时从Includes中引用文件,所以只需要在被引用的文件中设置对应注释即可. /** *@className: ${NAME} *@description: ...

  5. Kubernetes-5-2:Harbor仓库的几种高可用方案与搭建

    高可用Harbor搭建 思路及介绍 Harbor官方有推出主从架构和双主架构来实现Harbor的高可用及数据备份.   一.主从架构:  说白了,就是往一台Harbor仓库中push镜像,然后再通过这 ...

  6. Codeforces Round #585 (Div. 2) E. Marbles(状压dp)

    题意:给你一个长度为n的序列 问你需要多少次两两交换 可以让相同的数字在一个区间段 思路:我们可以预处理一个数组cnt[i][j]表示把i放到j前面需要交换多少次 然后二进制枚举后 每次选择一个为1的 ...

  7. [The Preliminary Contest for ICPC Asia Nanjing 2019] L-Digit sum

    题意 $S_{b}(n)$表示数字$n$在$b$进制下各位的和,对于给定的数$N$和$b$,求出$\sum_{n=1}^{N}S_{b}(n)$ $[ link ]$ 分析 题解上写的是签到题,这是个 ...

  8. P4587 [FJOI2016]神秘数(主席树)

    题意:给出1e5个数 查询l,r区间内第一个不能被表示的数 比如1,2,4可以用子集的和表示出[1,7] 所以第一个不能被表示的是8 题解:先考虑暴力的做法 把这个区间内的数字按从小到大排序后 从前往 ...

  9. [HDU-5172] 单点查询线段树

    题意: 给你一个长度为n的数组v[],有m次询问,问你在区间[L,R]中是否包含区间[1,R-L+1]的全部数字,如果是输出YES,否则输出NO 题解: 区间[1,R-L+1]与区间[L,R]的长度一 ...

  10. Codeforces Round #653 (Div. 3) B. Multiply by 2, divide by 6 (数学)

    题意:有一个数\(n\),每次操作可以使\(n*=2\)或\(n/=6\)(如果能被整除),求最少操作次数使得\(n=1\),如果不满足,输出\(-1\). 题解:我们只要看\(n\)的质因子即可,如 ...