前言
说到设计模式,面试排在第一位的十有八九是单例模式,这肯定是大部分人从入门到面试工作都避不开的基础知识。
但单例模式不仅有懒汉模式和饿汉模式两种写法,往往咱们把握的都是最根底的写法,如果你有浏览过相似 spring 这样的出名框架源码,肯定会发现他们的单例模式写法和你所把握的齐全不同。
本篇就给大家带来单例模式从根底 -> 最优 -> 额定举荐的写法,帮忙你面试疯狂加分。
懒汉饿汉
1、饿汉模式
饿汉模式简略了解就是提前创立好了对象
长处:写法简略,没有线程同步的问题
毛病:因为要提前创立好对象,不论应用与否都始终占着内存
举荐:对象较小且简略则应用饿汉模式
public final class Singleton {
// 创立好实例
private static Singleton instance = new Singleton();
// 构造函数
private Singleton() {}
// 获取实例
public static Singleton getInstance() {return instance;}
}
2、懒汉模式
懒汉模式简略了解就是在须要时才创建对象
长处:懒加载形式性能更高
毛病:要思考多线程的同步问题
举荐:只有不合乎下面饿汉的举荐应用条件则都应用懒汉模式
public final class Singleton {
private static Singleton instance = null;
// 构造函数
private Singleton() {}
// 获取实例
public static Singleton getInstance() {
// 为 null 时才实例化对象
if (null == instance) {instance = new Singleton();
}
return instance;
}
}
同步锁
下面介绍了懒汉的毛病是多线程同步的问题,那么马上就能想到应用同步锁来解决这个问题。
这里应用 synchronized 关键字且通过代码块来升高锁粒度,最大水平保障了性能开销,其实从 java8 当前,synchronized 的性能曾经有了较大晋升。
public final class Singleton {
private static Singleton instance = null;
// 构造函数
private Singleton() {}
// 获取实例
public static Singleton getInstance() {
// 获取对象时加上同步锁
if (null == instance) {synchronized (Singleton.class) {instance = new Singleton();
}
}
return instance;
}
}
双重查看锁
下面尽管应用了同步锁代码块,勉强解决了线程同步的问题且性能开销做了最大水平的优化,可实际上在多线程环境下依然存在线程平安问题。
当仍然有多个线程进入到 if 判断外面时,这个线程平安问题还是存在,尽管这种状况并非肯定呈现,可极其状况下呈现的几率十分大。
这个时候就须要应用面试中对于设计模式很喜爱问到的 DCL 即双重查看锁模式,听起来很高大上,其实就是多加了一层判断。
说白了,就是在进入同步锁之前和之后别离进行了查看,极大升高了线程平安问题。
public final class Singleton {
private static Singleton instance = null;
// 构造函数
private Singleton() {}
// 获取实例
public static Singleton getInstance() {
// 第一次判断,当 instance 为 null 时则实例化对象
if(null == instance) {synchronized (Singleton.class) {
// 第二次判断,放在同步锁中,当 instance 为 null 时则实例化对象
if(null == instance) {instance = new Singleton();
}
}
}
return instance;
}
}
最优双重查看锁
双重查看锁形式是单例懒汉模式在多线程下解决平安问题的最佳计划之一,但下面仍然不是最优写法。
这里就要引出一个「指令重排」的概念,这个概念是 java 内存模型中的,我这里用最简洁的形式帮你了解。
Java 中 new 一个对象在内存中执行指令的失常程序是:调配 -> 创立 -> 援用,而多线程环境下,JVM 出于对语句的优化,有可能重排程序:调配 -> 援用 -> 创立。
如果呈现这种状况,那么下面的双重查看锁形式仍然无奈解决线程平安问题。
解决形式很简略,加个 volatile 关键字即可。
volatile 关键字作用:保障可见性和有序性。
public final class Singleton {
// 加上 volatile 关键字
private volatile static Singleton instance = null;
// 构造函数
private Singleton() {}
// 获取实例
public static Singleton getInstance() {
// 第一次判断,当 instance 为 null 时则实例化对象
if(null == instance) {synchronized (Singleton.class) {
// 第二次判断,放在同步锁中,当 instance 为 null 时则实例化对象
if(null == instance) {instance = new Singleton();
}
}
}
return instance;
}
}
枚举懒汉模式
《Effective Java》是 Java 业界十分受欢迎的一本书,对于想要在 Java 畛域深耕的程序员来讲,这本书没有不看的理由,置信很多 Java 程序员不论看过还是没看过,都有听过这本书。
而这本书的作者,所举荐的一种单例设计模式写法,就是枚举形式。
原理非常简略,在 Java 中枚举类的域在编译后会被申明为 static 属性,而 JVM 会保障 static 润饰的成员变量只被实例化一次。
public class Singleton {
// 构造函数
private Singleton() {}
// 从枚举中获取实例
public static Singleton getInstance() {return SingletonEnum.SINGLETON.getInstance();
}
// 定义枚举
private enum SingletonEnum {
SINGLETON;
private Singleton instance;
// JVM 保障这个办法只调用一次
SingletonEnum() {instance = new Singleton();
}
public Singleton getInstance() {return instance;}
}
}
总结
最初这里略微提一下,免得局部人对于设计模式感到些许累赘。
单例模式其实很简略,饿汉模式和懒汉模式在许多开源框架中利用都比拟宽泛,甚至饿汉模式用的更多,比方 Java 的 Runtime 类中就这么干的,简略粗犷,有趣味的能够本人看下源码。
难道这些框架的作者就意识不到本篇中讲述的问题吗,并非如此,用哪种形式编写单例模式往往视状况而定,一些实践上会产生的问题往往理论中能够忽略不计,此时更偏向于应用最简略间接的写法。
真正难的其实还是面试,不少对于单例模式的问题中喜爱问到它的几种写法、存在的问题以及最佳计划,说白了还是面试造核弹,进厂拧螺丝,目标是想晓得你对设计模式的理解水平,从而评判你钻研该学科的态度及造诣。
因而,大家看完本篇能够手动尝试着写一写,理解一些也就够了,没必要过分深究,因为 Java 畛域须要破费精力的中央的确太多了。
心得
最初说下我的一点心得,所谓设计模式诚然能给 Java 代码自身带来更多优雅,然而写了很多年 Java 代码,我大体还是感觉 Java 自身的装璜切实太多,优雅换来的往往是代码自身的累赘。
我参加过的研发团队中,简直都能见到许多工程师编写的比拟优雅的代码,一些设计模式也写的很好,可带来的问题也很显著,就是可读性越来越差,要求每个团员都对 Java 有较高的造诣,甚至在某些时候给人力资源带来压力,这从理论角度思考是不妥的。
我更多的倡议是,应答面试或学习好好领悟设计模式,百利而无一害,但理论工作中尽量少用简单的设计模式,以简洁间接的代码为主,有利于整个团队前期保护,甚至放慢人员变更后新成员对我的项目的适应度,因为工作说白了还是以绩效为主,怎么简略高效怎么来就行,你本人的集体我的项目你想怎么玩轻易你。
自己原创文章纯手打,感觉有一滴滴帮忙的话就请点个赞和珍藏吧~
自己长期分享工作中的感悟、教训及实用案例,喜爱的话也能够进入个人主页关注一下哦~