标签:做了 系统 可见性 一个 总结 过程 targe 完美 问题
参考资料:
老司机来教你单例的正确姿势
《Android源码设计模式解析与实战》
单例模式可以说是应用最广泛的模式了, 面试也经常被问到, 经常会被要求能够手写单例, 所以我今天也来总结一下
确保某一个类只有一个实例, 并且自定实例化并向整个系统提供这个实例
确保某个类有且只有一个对象的场景, 避免产生多个对象消耗过多的资源, 或者某种类型的对象只应该有且只有一个.例如, 创建一个对象需要消耗的资源过多, 如要访问IO和数据库等资源
插点题外话说一下怎么看UML类图, 例如这里的Singleton矩形框就代表一个类, 第一层显示类的名称, 抽象类用斜体表示; 第二层是类的特性, 通常是字段和属性; 第三层是类的操作, 通常是方法. ‘+’表示public, ‘-‘表示private, ‘#’表示protected, 例如这里的getInstance()方法是public的, 构造方法是private的.
与类图有区别的是接口图, 顶端有<<interface>>
显示
实现单例模式有一下几个关键点:
1 | public class { |
这种单例的写法最简单,在类加载的时候就创建了单例对象, 并且保证了Sigleton对象的唯一性,以后不再改变,所以天生是线程安全的。但是缺点是一旦类被加载,单例就会初始化,没有实现懒加载。而且有时候这个对象的构造方法需要一个参数比如context的时候这种方法就不太适合了.
1 | public class { |
这段代码虽然实现了懒加载, 但是多线程环境下会出现线程安全的问题, 当多个线程调用getInstance()方法时, 可能会创建多个实例, 所以我们改成这样: 给getInstance()方法加个synchronized关键字.使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的.静态的synchronized方法它的锁对象就是该类的字节码对象
1 | public static synchronized Singleton getInstance() { |
但这样又出现了性能问题,简单粗暴的同步整个方法,导致同一时间内只有一个线程能够调用getInstance方法。
再次优化代码, 仅仅对初始化部分的代码进行同步
1 | public class { |
执行两次检测很有必要:当多线程调用时,如果多个线程同时执行完了第一次检查,其中第一个进入同步代码块的线程创建了实例,后面的线程因第二次检测不会创建新实例。这种写法还有一个名字叫做Double Check Locking(双重加锁),简称DCL.看似完美, 但是这段代码还是有问题.INSTANCE = new Singleton();
这个语句时其实做了三步工作,
1 | public class Singleton { |
使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说, 在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的 “后面” 是时间上的先后顺序)
到了这里就不得不说说, volatile这个关键字了, 也是个面试常客呢.我经常看到的说法是这样的, volatile可以保证在一个线程的工作内存中修改了该变量的值,该变量的值立即能回显到主内存中,从而保证所有的线程看到这个变量的值是一致的.参考这篇 面试官最爱的 volatile 关键字 和Java 之 volatile 详解我来简单谈谈.
被 volatile 修饰的共享变量,就具有了以下两点特性:
i = 8
举例说明:
1 | x = 10; //语句1 |
这个例子中,由于 volflag 被 volatile 修饰,所以语句 3 不会被重排到语句 1、语句 2 前面,也不会被重排到语句 4、语句 5 的后面,但语句 1、2 和语句 4、5 的顺序是不能保证的。
另外 volatile 可以保证在执行到语句 3 的时候语句 1、2 是执行完毕的,语句 4、5 是没有执行的,并且语句 1、2 的执行结果是对语句 4、5 是可见的
这里我只是参考了别人的文章关于 Java volatile 关键字简单总结了一下, 其实这个volatile深入起来还有很多东西可以说, 以后有时间的话我开一篇博客详细总结一下.
1 | public class Singleton { |
这是我平时比较喜欢用的单例, 使用内部类来维护单例的实例.当 Singleton 被加载时,其内部类并不会被初始化,故可以确保当 Singleton 类被载入 JVM 时,不会初始化单例类。只有 getInstance() 方法调用时,才会初始化 instance。同时,由于实例的建立是时在类加载时完成,故天生对多线程友好,getInstance() 方法也无需使用同步关键字。
1 | public enum Singleton { |
使用Singleton.INSTANCE.doSomething();
《Effective Java》一书推荐此方法,说 “单元素的枚举类型已经成为实现 Singleton 的最佳方法”。不过 Android 使用 enum 之后的 dex 大小增加很多,运行时还会产生额外的内存占用,因此官方强烈建议不要在 Android 程序里面使用到enum
在实际开发过程中, 我比较喜欢用内部类的单例模式, 其次是安全的DCL模式, 再次是饿汉式, 其他的基本不会使用.
标签:做了 系统 可见性 一个 总结 过程 targe 完美 问题
原文地址:https://www.cnblogs.com/dajunjun/p/11712830.html