单例对象的类只能允许一个实例存在,意味着只有通过该类提供的静态方法来得到该类的唯一实例,开发过程中我们通常需要一个全局对象进行一些数据、配置等的管理,但在开发过程中经常会有多线程的交互问题,因此我们使用单例模式时,写法可采用 饿汉式、静态内部类、枚举方式实现,其中静态内部类方式对我们Android开发来说更适合。
1. 饿汉式 空间换时间
优点:写法简单,类装载时就完成实例化,避免了线程同步问题
缺点:类装载时就完成实例化意味着没有达到延迟装载的效果,意味着就算不使用这个类也会占据着内存
class SingletonOne{
private static SingletonOne singletonOne = new SingletonOne();
private SingletonOne() {
}
public static SingletonOne getInstance() {
return singletonOne;
}
}
2.静态内部类 延迟加载,线程安全
优点:对比饿汉式方式来说既实现了线程安全的单例,又能满足延迟加载的效果,且效率高
SingletoHolder在SingleTwo被装载时并不会立即实例化,而在getInstance时才会装载,类的静态属性只会在第一次加载类的时候初始化,所以这里是靠JVM的装载机制保证了线程的安全性
class SingletonTwo{
private SingletonTwo() {
}
private static class SingletonHolder{
private static SingletonTwo singletonTwo = new SingletonTwo();
}
public static SingletonTwo getInstance() {
return SingletonHolder.singletonTwo;
}
}
3.枚举方式
优点:因为默认枚举实例的创建是线程安全的并在任何情况下都是一个单例
缺点:在Android中枚举占据的内存会比静态常量超过两倍多,所以尽量避免使用枚举
enum SingletonThree{
INSTANCE;
public void doSomething(){};
}
4. 不推荐写法 时间换空间
此处基本是懒汉式方法的变种,演变趋至复杂,不建议这样写
懒汉式-1
优点:实现了延迟加载
缺点:只能在单线程下使用
class SingletonF_1 {
private static SingletonF_1 singletonF_1;
private SingletonF_1() {
}
public static SingletonF_1 getInstance() {
singletonF_1 = new SingletonF_1();
return singletonF_1;
}
}
懒汉式-2
优点:实现了线程安全,延迟加载
缺点:每次获取实例都要进入同步方法,效率低下
class SingletonF_2 {
private static SingletonF_2 singletonF_2;
private SingletonF_2() {
}
public static synchronized SingletonF_2 getInstance() {
singletonF_2 = new SingletonF_2();
return singletonF_2;
}
}
懒汉式-3
优点:延迟加载,解决上方的每次都进入同步问题
缺点:带来了线程不安全,当两个线程都走到判空处时,两个线程先后执行同步块方法将生成两个对象
class SingletonF_3 {
private static SingletonF_3 singletonF_3;
private SingletonF_3() {
}
public static SingletonF_3 getInstance() {
if (singletonF_3 == null) {
synchronized (SingletonF_3.class){
singletonF_3 = new SingletonF_3();
}
}
return singletonF_3;
}
}
懒汉式-4 又称双重校验
优点:延迟加载,解决上方的线程不安全问题
缺点:这种写法是完美理论但在执行过程中,因为指令重排序的问题,可能导致引用的对象虽不为空但还未完成实例化导致出错
指令重排序:JVM对语句执行的优化,只要语句间没有依赖,那JVM就可能会对它们进行重排序
如singletonF_4 = new SingletonF_4();
这段代码不具有原子性,在执行过程中会被拆分为好几步
- 分配空间给对象
- 在空间内创建对象
- 将对象赋值给引用singlteoF_4
2依赖于1,但2与3不存在依赖,因此他的执行顺序既可能是1>2>3,也可能是1>3>2,如果是1>3>2,当线程A执行到3时,线程B执行到第一次判空,此时的singlteoF_4已经不为null但是还未完全初始化,此时会发生异常
class SingletonF_4 {
private static SingletonF_4 singletonF_4;
private SingletonF_4() {
}
public static SingletonF_4 getInstance() {
if (singletonF_4 != null) {
synchronized (SingletonF_4.class){
if (singletonF_4 == null) {
singletonF_4 = new SingletonF_4();
}
}
}
return singletonF_4;
}
}
懒汉式-5 双重校验优化--volatile 关键字
优点:解决指令重排序
缺点:需要在本地代码中插入许多的内存屏障指令保证不发生乱序执行,这样导致程序执行变慢
volatile主要作用
- 可见性 volatile 修饰的变量一旦发生更改会立即更新到主存当中去,未被修饰的变量则不可预估
- 有序性 禁止指令重排序可以保证初始化的有序性
class SingletonF_5 {
private volatile static SingletonF_5 singletonF_5;
private SingletonF_5() {
}
public static SingletonF_5 getInstance() {
if (singletonF_5 != null) {
synchronized (SingletonF_5.class){
if (singletonF_5 == null) {
singletonF_5 = new SingletonF_5();
}
}
}
return singletonF_5;
}
}
懒汉式-6 双重校验优化--变相保证有序执行
优点:解决指令重排序的
class SingletonF_6 {
private static SingletonF_6 singletonF_6;
private SingletonF_6() {
}
public static SingletonF_6 getInstance() {
if (singletonF_6 != null) {
synchronized (SingletonF_6.class) {
SingletonF_6 temp = null;
try {
temp = new SingletonF_6();
} catch (Exception e) {
}
if (temp != null){
//为什么要做这个看似无用的操作,因为这一步是为了让虚拟机执行到这一步的时会才对singleton赋值,
// 虚拟机执行到这里的时候,必然已经完成类实例的初始化。
// 所以这种写法的DCL(Double Check LockDCL双重检查锁)是安全的。
// 由于try的存在,虚拟机无法优化temp是否为null
singletonF_6 = temp;
}
}
}
return singletonF_6;
}
}
参考链接:
指令重排序 https://blog.csdn.net/a_842297171/article/details/79316591
懒汉式优化 https://www.jianshu.com/p/c80ac10db854
volatile关键字https://blog.csdn.net/nugongahou110/article/details/49927667
网友评论