23种设计模式面试题(互联网面试题-请简单介绍一下单例设计模式)
单例设计模式
单例设计模式是GOF23种设计模式中最简单的一种设计模式,也是Java编程中入门级别的设计模式。其定义就是在某种情况下,一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
在计算机系统中,还有Windows的回收站、操作系统中的文件系统、多线程中的线程池、显卡驱动、打印机的后台处理线程、应用程序的日志对象、数据库连接池、网站计数器等等,在设计的时候都被设计成了单例模式。
用途在实际开发中单例设计模式主要有如下的一些优势
- 由于单例设计模式,在内存中只存在一个对象实例,从内存消耗的角度上来讲,尤其是对于一些频繁的创建或者是销毁实例的操作,是非常节省内存消耗的。
- 避免了对于资源文件的多重占用,例如我们在使用打印机的时候,只需要一个打印机控制程序即可,而不是对每个需要打印的内容都开启一个打印机的控制程序,这样如果打印的内容太多的话就会造成资源的浪费。
在实际开发中,在有些业务中可能是因为业务的需求,所以必须要采用到单例设计模式。
实现单例设计模式在之前的文章中我们介绍过关于单例设计模式,同时我们介绍了单例设计模式的设计原则
- 一个私有的构造方法
- 在类体中创建好一个对象
- 在类体中创建一个提供对外获取该对象的方法。
可以简单的将其理解为成员变量私有化,并且提供get方法。下面我们就来看看具体的单例设计模式有哪些实现方式。
饿汉式
饿汉式实现方式作为在我们实现单例模式中最为常用的一种实现方式,其代码如下。
public class Singleton {
//在类内部实例化一个实例
private static Singleton instance = new Singleton();
//私有的构造函数,外部无法访问
private Singleton() {
}
//对外提供获取实例的静态方法
public static Singleton getInstance() {
return instance;
}
}
所谓的饿汉式实现方式顾名思义就是说当你需要使用这个对象的时候你就可以立即获取到该对象,而不需要等待的时间,所以在创建对象的时候后采用的static关键字来进行初始化,这样可以保证在类被第一次加载的时候对象就会被创建,也就会保证在后续访问的时候可以直接获取到该对象。
由于该对象的创建是在类初始化的时候就被创建的,所以也就避免了后续的线程安全相关的问题。也就是说饿汉式设计是天然的线程安全、调用效率高、不加延迟操作。下面这种方式是饿汉式的一种变种,实现的效果与上面的代码实现的效果是一样的。
public class Singleton2 {
//在类内部定义
private static Singleton2 instance;
static {
//实例化该实例
instance = new Singleton2();
}
//私有的构造函数,外部无法访问
private Singleton2() {
}
//对外提供获取实例的静态方法
public static Singleton2 getInstance() {
return instance;
}
}
对于饿汉式单例模式,是在类被加载的时候对象就进行了实例化,所以就会造成一些不必要的内存消耗,因为可能被初始化的实例对象在整个的代码周期中就用不到,而且如果这个类被多次加载的话也会出现多个实例的情况,为了解决这个问题,就有了下面这种方式。
静态内部类式静态内部类解决饿汉式加载问题
public class StaticInnerClassSingleton {
//在静态内部类中初始化实例对象
private static class SingletonHolder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
//私有的构造方法
private StaticInnerClassSingleton() {
}
//对外提供获取实例的静态方法
public static final StaticInnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式通过类加载机制来保证初始化的时候只有一个Instance实例。它与饿汉式的不同就在于饿汉式只要Singleton类被装载了,那么instance就会被实例化,也就是说没有达到懒加载的效果。而采用静态内部类的方式就是说Singleton被加载了,instance也不一定会被初始化,因为SingletonHolder类没有被主动的使用,只有调用了getInstance方法的时候SingletonHolder类才对instance进行了实例化。所以这种方式实现了对Instance对象的懒加载操作,也就是说在使用的时候在进行初始化。
懒汉式下面我们来展示一种懒加载的单例设计模式,懒汉式,代码如下。
public class Singleton {
//定义实例
private static Singleton instance;
//私有构造方法
private Singleton(){}
//对外提供获取实例的静态方法
public static Singleton getInstance() {
//在对象被使用的时候才实例化
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上面的这种实现方式被称为是懒汉式单例模式,所以为的懒汉式就是不会提前将实例创建出来,而是在第一次使用的时候,通过getInstance方法来创建一个实例对象。但是这种方式会出现线程安全问题,在多线程的情况下如果第一个线程调用了getInstance方法,第二个线程继续调用getInstance方法这样就会导致两个线程创建两个Instance实例对象。这样其实就不是我们想要的那种单例模式了。
线程安全的懒汉式实现既然我们说上面这种实现方式是线程不安全的,那么下面我们就来看一种线程安全的设计模式。
public class synchronizedSingleton {
//定义实例
private static SynchronizedSingleton instance;
//私有构造方法
private SynchronizedSingleton(){}
//对外提供获取实例的静态方法,对该方法加锁
public static synchronized SynchronizedSingleton getInstance() {
//在对象被使用的时候才实例化
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
这种方式解决了多线程线程安全的问题,而且也有延迟加载的效果,但是,它的执行效率确实非常低的,因为对于单例模式来讲,既然全局都需要同一个对象,那么也就是对象全局只会被创建一次,而后续的操作中则不需要再次创建这个对象,那么在创建方法上添加Synchroinzed关键字来实现同步就不需要每次都进行同步操作了。这样就出现了我们下面这种实现方式。
双重校验锁针对上面代码中存在的问题,我们提出了如下的这种解决方案,既然上面的代码中存在的问题是锁的范围过大,那么我们就来减小锁的范围。代码如下。
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
上面这种写法其实是线程安全的懒汉式写法的改进操作,通过缩小同步代码的范围的方式来提升执行效率。但是这个代码看上去并没有什么问题,实际上在很多的编程老师在讲解这个地方的都会提到Java的内存模型,对双重检测锁模式的影响。
关于这个知识点这里我们不做展开的说明,有兴趣的读者可以自行查阅相关资料。
针对上面的问题,我们提出了如下的两种解决方案。
使用Volatile关键字
public class VolatileSingleton {
private static volatile VolatileSingleton singleton;
private VolatileSingleton() {
}
public static VolatileSingleton getSingleton() {
if (singleton == null) {
synchronized (VolatileSingleton.class) {
if (singleton == null) {
singleton = new VolatileSingleton();
}
}
}
return singleton;
}
}
使用final
class FinalWrapper<T> {
public final T value;
public FinalWrapper(T value) {
this.value = value;
}
}
public class FinalSingleton {
private FinalWrapper<FinalSingleton> helperWrapper = null;
public FinalSingleton getHelper() {
FinalWrapper<FinalSingleton> wrapper = helperWrapper;
if (wrapper == null) {
synchronized (this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<FinalSingleton>(new FinalSingleton());
}
wrapper = helperWrapper;
}
}
return wrapper.value;
}
}
在JDK1.5之前,单例模式一般只有前面的几种是实现方式,在JDK1.5之后,出现了基于枚举类型的单例实现方式。
public enum Singleton {
INSTANCE;
Singleton() {
}
}
这种实现方式,不仅避免了多线程线程同步问题,而且还可以防止由于反序列化而创建新的对象等问题。但是实际开发过程中我们很少采用这种方式来实现单例设计模式。
总结上面我们介绍了几种实现单例设计模式的方法,并且介绍了各种方式的优缺点。希望大家多多关注,后续还会为大家带来很多面试干货。
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。