最适合作为Java基础面试题之Singleton模式

时间:2022-09-25 10:49:46

看似只是最简单的一种设计模式,可细细挖掘,static、synchronized、volatile关键字、内部类、对象克隆、序列化、枚举类型、反射和类加载机制等基础却又不易理解透彻的Java知识纷纷呼之欲出,让人不禁感叹Singleton单例模式是最适合作为考察应聘者Java基础的一道考题。
从表面上看,Singleton希望并限制该类的实例只能有一个,如JDK自带的Runtime类,其构造方式通常是一个private构造函数、static的该类实例、以及返回该实例的getInstance方法。

最适合作为Java基础面试题之Singleton模式

那接下来我们就看看实现一个Singleton究竟有哪几种方式。

1. Eager Singleton

public class EagerSingleton {

    private static final EagerSingleton INSTANCE = new EagerSingleton();

    private EagerSingleton() {
} public static EagerSingleton getInstance() {
return INSTANCE;
}
}

这种称为“饿汉式”的实现方式可能是最简单也最常见的,顾名思义,该实例在类加载的时候就会自动创建不管之后是否被使用。所以如果该类实例化的开销比较大,这种方式或许就不太理想,不过它的优点也很明显,即无需担心多线程同步获取该实例时可能出现的并发问题。

2. Lazy Singleton

public class LazySingleton {

    private volatile static LazySingleton INSTANCE = null;

    private LazySingleton() {
} public static synchronized LazySingleton getInstance() {
if (INSTANCE == null)
INSTANCE = new LazySingleton();
return INSTANCE;
}
}

这种方式也有个形象的名字“懒汉式”,既然觉得类加载时就完成实例化有点浪费,那不如将这一过程推迟到实际需要使用时,可是在此值得注意的是为了避免多线程并发场景下可能导致的莫名其妙多创建出一个实例的弊端,getInstance方法必须标记为synchronized方法或采用synchronized代码块来加锁实现。但是这种过度保护的代价是非常高昂的,其实只有当该实例未被创建时才有必要加锁控制并发,因此更多时候是没必要同步的,此类方式并不经济划算。

3. Lazy Singleton with Double Check

public class LazySingletonWithDoubleCheck {

    private volatile static LazySingletonWithDoubleCheck INSTANCE = null;

    private LazySingletonWithDoubleCheck() {
} public static LazySingletonWithDoubleCheck getInstance() {
if (INSTANCE == null) {
synchronized (LazySingletonWithDoubleCheck.class) {
if (INSTANCE == null)
INSTANCE = new LazySingletonWithDoubleCheck();
}
}
return INSTANCE;
}
}

作为Lazy Singleton的改良版,这种采用了double-check的实现方式避免了对getInstance方法总是加锁。注意到尚未实例化时,存在两次检查的流程,第一次检查如果发现该实例已经存在就可以直接返回,反正则加类锁并进行第二次检查,原因在于可能出现多个线程同时通过了第一次检查,此时必须通过锁机制实现真正实例化时的排他性,保证只有一个线程成功抢占到锁并执行。此举即保证了线程安全,又将性能折损明显降低了,不失为比较理想的做法。

4. Inner Class Singleton

public class InnerClassSingleton {

    private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
} private InnerClassSingleton() {
} public static final InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

另外一种可以有效解决线程并发问题并延迟实例化的做法就是如上代码所示的利用静态内部类来实现。单例类的实例被包裹在内部类中,因此该单例类加载时并不会完成实例化,直到有线程调用getInstance方法,内部类才会被加载并实例化单例类。这种做法应该说是比较令人满意的。

以上就是比较常见的实现Singleton的方式,这也是一般的Java面试所涉及的深度。可是带有好奇心的人不禁会琢磨所谓的单例真的可以保证全局唯一性吗?能不能采用一些tricky 的方式去破坏这一核心属性呢?这才是本文着重介绍的部分,因为其覆盖了多个重要的知识点。接下来我们就一起看看通过哪些看似合法的手段可以有效绕开传统Singleton实现中仅靠将构造函数私有化达成的单例从而创建出多个实例。

1. Break Singleton with Clonable

public class ClonableSingleton implements Cloneable{

    private static final ClonableSingleton INSTANCE = new ClonableSingleton();

    private ClonableSingleton() {
} public static ClonableSingleton getInstance() {
return INSTANCE;
} public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}

没错,第一种比较容易想到的方式就是clone,Java中类通过实现Clonable接口并覆写clone方法就可以完成一个其对象的拷贝。而当Singleton类为Clonable时也自然无法避免可利用这种方式被重新创建一份实例。通过以下的测试代码即可检验通过clone我们可以有效破坏单例。

public static void checkClone() throws Exception {
ClonableSingleton a = ClonableSingleton.getInstance();
ClonableSingleton b = (ClonableSingleton) a.clone(); assertEquals(a, b);
}

2. Break Singleton with Serialization

public class SerializableSingleton implements Serializable{

    private static final long serialVersionUID = 6789088557981297876L;

    private static final SerializableSingleton INSTANCE = new SerializableSingleton();

    private SerializableSingleton() {
} public static SerializableSingleton getInstance() {
return INSTANCE;
}
}

第二种破坏方式就是利用序列化与反序列化,当Singleton类实现了Serializable接口就代表它是可以被序列化的,该实例会被保存在文件中,需要时从该文件中读取并反序列化成 对象。可就是在反序列化这一过程中不知不觉留下了可趁之机,因为默认的反序列化过程是绕开构造函数直接使用字节生成一个新的对象。于是,Singleton在反序列化时被创造出第二个实例。通过如下代码可轻松实现这一行为,a与b最终并不相等。

public static void checkSerialization() throws Exception {
File file = new File("serializableSingleton.out");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
file));
SerializableSingleton a = SerializableSingleton.getInstance();
out.writeObject(a);
out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
SerializableSingleton b = (SerializableSingleton) in.readObject();
in.close(); assertEquals(a, b);
}

3. Break Singleton with Reflection

public static void checkReflection() throws Exception {
EagerSingleton a = EagerSingleton.getInstance(); Constructor<EagerSingleton> cons = EagerSingleton.class
.getDeclaredConstructor();
cons.setAccessible(true);
EagerSingleton b = (EagerSingleton) cons.newInstance(); assertEquals(a, b);
}

前两种破坏方式说到底都是通过避免与私有构造函数正面冲突的方式另辟蹊径来实现的,而这种方式就显得艺高人胆大,既然你是私有的不允许外界直接调用,那么我就利用反射机制强行逼你就范:公开其访问权限。如此一来,原本看似安全的堡垒顷刻间沦为炮灰,Singleton再次沦陷。

4. Break Singleton with Classloaders

public static void checkClassloader() throws Exception {
String className = "fernando.lee.singleton.EagerSingleton";
ClassLoader classLoader1 = new MyClassloader();
Class<?> clazz1 = classLoader1.loadClass(className); ClassLoader classLoader2 = new MyClassloader();
Class<?> clazz2 = classLoader2.loadClass(className); System.out.println("classLoader1 = " + clazz1.getClassLoader());
System.out.println("classLoader2 = " + clazz2.getClassLoader()); Method getInstance1 = clazz1.getDeclaredMethod("getInstance");
Method getInstance2 = clazz2.getDeclaredMethod("getInstance");
Object a = getInstance1.invoke(null);
Object b = getInstance2.invoke(null); assertEquals(a, b);
}

Java中一个类并不是单纯依靠其全包类名来标识的,而是全包类名加上加载它的类加载器共同确定的。因此,只要是用不同的类加载器加载的Singleton类并不认为是相同的,因此单例会再次被破坏,通过自定义编写的MyClassLoader即可实现。

由此看来,Singleton唯有妥善关闭了如上所述的诸多后门才能称得上真正的单例。笔者了解到通常有两种应对措施:现有基础上堵住所有漏洞和摈弃旧貌采取创新。简而言之,第一种方式通过完善现有实现让克隆、序列化、反射和类加载器无从下手,第二种方式则采取枚举类型间接实现单例。

1. Safe Singleton

public class SafeSingleton implements Serializable, Cloneable {

    private static final long serialVersionUID = -4147288492005226212L;

    private static SafeSingleton INSTANCE = new SafeSingleton();

    private SafeSingleton() {
if (INSTANCE != null) {
throw new IllegalStateException("Singleton instance Already created.");
}
} public static SafeSingleton getInstance() {
return INSTANCE;
} private Object readResolve() throws ObjectStreamException {
return INSTANCE;
} public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Singleton can't be cloned");
}
}

在原有Singleton的基础上完善若干方法即可实现一个安全的更为纯正的Singleton。注意到当实例已经存在时试图通过调用私有构造函数会直接报错从而抵御了反射机制的入侵; 让调用clone方法直接报错避免了实例被克隆;覆写readReslove方法直接返回现有的实例本身可以防止反序列化过程中生成新的实例。而对于不同类加载器导致的单例模式破坏笔者暂 未亲测出切实可行的应对方案,还烦请大牛提供高见。

2. Enum Singleton

public enum EnumSingleton{
INSTANCE; private EnumSingleton(){
}
}

采用枚举的方式实现Singleton非常简易,而且可直接通过EnumSingleton.INSTANCE获取该实例。Java中所有定义为enum的类内部都继承了Enum类,而Enum具备的特性包括类加载是静态的来保证线程安全,而且其中的clone方法是final的且直接抛出CloneNotSupportedException异常因而不允许拷贝,同时与生俱来的序列化机制也是直接由JVM掌控的并不会创建出新的实例,此外Enum不能被显式实例化反射破坏也不起作用。当然它也不是没有缺点,比如由于已经隐式继承Enum所以无法再继承其他类了(Java的单继承模式限制),而且相信大多数人并不乐意仅仅为了实现一个纯正的Singleton就将更为习惯的class修改为enum。

  本文介绍了基础篇和进阶篇的Singleton,看似浅显易懂的单例模式没想到也涵盖了那么多学问,希望大家看了有所收获,如果有兴趣亲自实践一番相信更是大有裨益。

  

References:
http://www.tuicool.com/articles/uuuy2m
http://www.javacodegeeks.com/2014/05/java-singleton-design-pattern.html
http://javarevisited.blogspot.com/2011/03/10-interview-questions-on-singleton.html
http://javarevisited.blogspot.com/2012/07/why-enum-singleton-are-better-in-java.html
http://blog.csdn.net/fg2006/article/details/6409423

  


为尊重原创成果,如需转载烦请注明本文出处:http://www.cnblogs.com/fernandolee24/p/5366720.html,特此感谢

  

最适合作为Java基础面试题之Singleton模式的更多相关文章

  1. 一份最贴近真实面试的Java基础面试题

    这是一份Java基础知识的面试题.在网上的关于Java的面试题数不胜数,但认真看过感觉大多数都没有实用性,有很多是面试官根本就不会问到的,那些已经脱离了实际开发的技术问题.而这份资料来源自一份个人觉得 ...

  2. 28道java基础面试题-下

    28道java基础面试题下 15.Java语言如何进行异常处理,关键字:throws.throw.try.catch.finally分别如何使用? 答:Java通过面向对象的方法进行异常处理,把各种不 ...

  3. Java基础面试题集&lpar;一&rpar;

    Java基础面试题 一.面向对象编程(OOP) 7 二.常见的Java问题 7 2.1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? 7 2.2.JDK和JRE的区别是什么? ...

  4. 经典的Java基础面试题集锦

    经典的Java基础面试题集锦,欢迎收藏和分享. 问题:如果main方法被声明为private会怎样? 答案:能正常编译,但运行的时候会提示”main方法不是public的”. 问题:Java里的传引用 ...

  5. 非常全面的java基础笔试题

    下面是java基础笔试题,当时我去笔试,做了1个小时(80道选择题,后面的简答题就没时间做了),结果很吓人,太挫了,最后被面试官忽悠去培训去了,呵呵.我偷偷把面试题弄了下来,用来学习吧,也希望能对你们 ...

  6. Java基础机试题

    package day8;import java.util.Scanner;/** * Java基础机试题 * @author:lyrand * */public class convert {   ...

  7. 100道Java基础面试题收集整理(附答案)

    不积跬步无以至千里,这里会不断收集和更新Java基础相关的面试题,目前已收集100题. 1.什么是B/S架构?什么是C/S架构 B/S(Browser/Server),浏览器/服务器程序 C/S(Cl ...

  8. 大数据学习--day04&lpar;选择结构、循环结构、大数据java基础面试题&rpar;

    选择结构.循环结构.大数据java基础面试题 switch: 注意: byte short int char String(jdk1.7支持) 不能是 long float double boolea ...

  9. 最新28道java基础面试题-上

    28道java基础面试题 1.面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面.抽象只关注对象有哪 ...

随机推荐

  1. git初学习体会

    github:项目版本控制器 git和传统的版本控制器相比,最大的一点是,界面简单,给与非线性开发模式的强有力的支持,完全分布式等. 对于完全分布式的实现,我的理解是这个样子的.这多少要涉及到一点它的 ...

  2. ZOJ 3903 Ant(数学,推公示&plus;乘法逆元)

    Ant Time Limit: 1 Second      Memory Limit: 32768 KB There is an ant named Alice. Alice likes going ...

  3. java&period;lang&period;ClassCastException&colon; sun&period;proxy&period;&dollar;Proxy11 cannot be cast to分析

    报这个错,只有一个原因,就是你转化的类型不对. 如果你的类是一个单实体类,也就是没有继承或是接口别的类. public class HjmServiceImpl {} 那么这样写就可以: HjmSer ...

  4. There is no getter for property named &&num;39&semi;XXX&&num;39&semi; in &&num;39&semi;class java&period;lang&period;String&&num;39&semi;解决方法

    <select id="ProjectHomePage" parameterType="string" resultType="java.uti ...

  5. Jmeter插件安装及使用

    1 安装Plugins Manager插件 1.1 下载Plugins Manager插件 插件下载官方地址:https://jmeter-plugins.org/downloads/all/ 将下载 ...

  6. RHCSA考试真题

    2018年 RHCSA考试真题... ------------ 考前需要做的基础 破解root密码 KVM虚拟机与VM虚拟机 主机名:station.domain1.example.comIP地址:1 ...

  7. python&comma;os方法的简单介绍

    ''' 这一个章节是学习os及os.path的用法 ''' #学习os首先需要引入os文件,imoprt os import os #getcwd()的用法,它是返回当前的工作目录,说白了就是你的程序 ...

  8. day5&lowbar;非空即真非零即真

    非空即真(字符串.元组.列表.字典.None),非零即真(指的是int类型或数字这种) # d={}# l=[]# t=()# a = ''# b = None以上都代表空 举例1: name = i ...

  9. C&num; 往线程里传参数的方法总结

    Thread (ParameterizedThreadStart) 初始化 Thread 类的新实例,指定允许对象在线程启动时传递给线程的委托.   Thread (ThreadStart) 初始化 ...

  10. Python异常&lpar;基础&rpar; except

    为什么要异常处理机制:在程序调用层数较深时,向主调函数传递错误信息需要层层return 返回比较麻烦,用异常处理机制可以较简单的传送错误信息 什么是错误 错误是指由于逻辑或语法等导致一个程序已无法正常 ...