public class Singleton {
private static final Singleton singleton = new Singleton();
//构造函数私有化
private Singleton() {
}
//公有的静态函数,对外暴露获取单例对象的接口
public static Singleton getInstance() {
return singleton;
}
}
饿汉单例模式采用的是静态变量 + fianl关键字的方式来确保单例模式,应用启动的时候就生成单例对象,效率不高
public class Singleton {
private static Singleton singleton;
//构造函数私有化
private Singleton() {
}
//公有的静态函数,对外暴露获取单例对象的接口
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
懒汉模式的主要是加了synchronized关键字,每调用一次getInstance方法,都会进行同步,造成了不必要的开销
public class Singleton{
// 静态属性,volatile保证可见性和禁止指令重排序
private volatile static Singleton instance = null;
// 私有化构造器
private Singleton(){}
public static Singleton getInstance(){
// 第一重检查锁定
if(instance==null){
// 同步锁定代码块
synchronized(Singleton.class){
// 第二重检查锁定
if(instance==null){
// 注意:非原子操作
instance=new Singleton();
}
}
}
return instance;
}
}
-
DCL模式是使用最多的单例模式,它不仅能保证线程安全,资源利用率高,第一次执行getInstance时单例对象才会实例化;同时,后续调用getInstance方法时又不会有懒汉模式的重复同步的问题,效率更高;在绝大多数情况下都能保证单例对象的唯一性
-
DCL模式需要注意要用volatile关键字,否则还是会导致创建多个实例
-
DCL模式的缺点是第一次加载时由于需要同步反应会稍慢;在低于JDK1.5的版本里由于Java内存模型的原因有可能会失效
volatile
volatile 是DCL模式的关键,他保证了可见性和禁止指令重排序,一般情况下android程序员都会忘记加volatile!!!!
-
禁止指令重排序。 我们知道new Singleton()是一个非原子操作,编译器可能会重排序【构造函数可能在整个对象初始化完成前执行完毕,即赋值操作(只是在内存中开辟一片存储区域后直接返回内存的引用)在初始化对象前完成】。而线程B在线程A赋值完时判断instance就不为null了,此时B拿到的将是一个没有初始化完成的半成品。
-
保证可见性。 线程A在自己的工作线程内创建了实例,但此时还未同步到主存中;此时线程B在主存中判断instance还是null,那么线程B又将在自己的工作线程中创建一个实例,这样就创建了多个实例。
什么是指令重排序?有两个层面:
-
在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU。拿上面的例子来说:假如不是a=1的操作,而是a=new byte1024*1024`,那么它会运行地很慢,此时CPU是等待其执行结束呢,还是先执行下面那句flag=true呢?显然,先执行flag=true可以提前使用CPU,加快整体效率,当然这样的前提是不会产生错误(什么样的错误后面再说)。虽然这里有两种情况:后面的代码先于前面的代码开始执行;前面的代码先开始执行,但当效率较慢的时候,后面的代码开始执行并先于前面的代码执行结束。不管谁先开始,总之后面的代码在一些情况下存在先结束的可能。
-
在硬件层面,CPU会将接收到的一批指令按照其规则重排序,同样是基于CPU速度比缓存速度快的原因,和上一点的目的类似,只是硬件处理的话,每次只能在接收到的有限指令范围内重排序,而虚拟机可以在更大层面、更多指令范围内重排序。硬件的重排序机制参见《从JVM并发看CPU内存指令重排序(Memory Reordering)》
重排序很不好理解,上面只是简单地提了下其场景,要想较好地理解这个概念,需要构造一些例子和图表,在这里介绍两篇介绍比较详细、生动的文章《happens-before俗解》和《深入理解Java内存模型(二)——重排序》。其中的“as-if-serial”是应该掌握的,即:不管怎么重排序,单线程程序的执行结果不能被改变。编译器、运行时和处理器都必须遵守“as-if-serial”语义。
引用于:java指令重排序的问题
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
//静态内部类
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
-
第一次加载Singleton类时不会初始化sInstance,只有在第一次调用getInstance方法时才会初始化sInstance,延迟了单例对象的实例化
-
静态内部类单例模式不仅能保证线程安全也能保证单例对象的唯一性
推荐使用饿汉模式和静态内部类方式实现单例模式
最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)
学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。
下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!
——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
PW4LEk85-1714872907277)]
除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!
——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!