单例模式是面试中很常问的问题,一般在面试 web 岗位时,可能会涉及算法较少,手写单例模式时有发生,我有个朋友在上次跳槽时有三家都是让现场手写一个。
如果想直接对线面试官代码,建议看文末的·方法五
有什么好处:
- 了解应聘者编码能力
- 观察应聘者思维缜密程度
言归正传,看看单例模式到底怎么写
单例(Singleton)模式的定义:
指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
单例模式的结构与实现:
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过 “new 构造函数()” 来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
使用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。(这个应该是使用最广泛的场景,有时我们会创建一个kafka客户端、或者其他的客户端会使用到单例模式)
到底怎么写?
方法一:
简易版:大家第一种想到的方式,肯定是这样
public class SingleObjectJavaPub { //创建 SingleObject 的一个对象 private static SingleObjectJavaPub instance = new SingleObjectJavaPub(); //让构造函数为 private,这样该类就不会被实例化 private SingleObjectJavaPub(){} //获取唯一可用的对象 public static SingleObjectJavaPub getInstance(){ return instance; } }
方法二:
懒汉模式,线程不安全,这种方式 lazy loading 很明显
public class SingletonJavaPub { private static SingletonJavaPub instance; private SingletonJavaPub(){} public static SingletonJavaPub getInstance() { if (instance == null) { instance = new SingletonJavaPub(); } return instance; } }
方法三:
懒汉式,线程安全,加了 synchronized ,加锁一定会影响效率,但是 getInstance() 的性能对应用程序不是很关键的情况(该方法使用不太频繁)。
public class SingletonJavaPub { private static SingletonJavaPub instance; private SingletonJavaPub(){} public static synchronized SingletonJavaPub getInstance() { if (instance == null) { instance = new SingletonJavaPub(); } return instance; } }
方法四:
饿汉式,这个是比较难一点理解的,静态方法在初始化时,可能有很多因素。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
public class SingletonJavaPub { private static SingletonJavaPub instance = new SingletonJavaPub(); private SingletonJavaPub(){} public static SingletonJavaPub getInstance() { return instance; } }
方法五:
双检锁/双重校验锁(DCL,即 double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键的情况。
public class SingletonJavaPub { private volatile static SingletonJavaPub singleton; private SingletonJavaPub (){} public static SingletonJavaPub getSingleton() { if (singleton == null) { synchronized (SingletonJavaPub .class) { if (singleton == null) { singleton = new SingletonJavaPub(); } } } return singleton; } }