Android 单例模式

来源:互联网 发布:4g网络玩游戏延迟高 编辑:程序博客网 时间:2024/05/16 23:59

介绍

单例模式是一种对象创建模式,它用于创建一个对象的具体实例,并确保系统中有且仅有一个该对象的实例。

单例好处

  1. 对于频繁使用的对象,可以节省创建多个新的对象所需要的时间
  2. 由于对象创建频率低,对系统内存的使用频率降低,可以减轻垃圾回收的压力。毕竟过于频发的 GC 会引起卡顿。

饿汉

public class Hungry {  private static Hungry instance = new Hungry(); private Hungry(){    //do something  }  public static Hungry getInstance(){    return instance;  }  public static void printSomething(){    System.out.println("print something in Hungry");  }}

因为使用了 static 关键字,所以该方法的单例类会在第一次引用该类的时候就创建对象实例。

优点

结构简单,线程安全

缺点

达不到按需加载。例如,printSomething() 这个方法,本来调用该方法是不需要创建该对象实例的,但由于实现的方法还是创建了该类对象。

懒汉

实现一

public class SimpleLazy{  private static SimpleLazy instance = null; private SimpleLazy(){    //do something  }  public static SimpleLazy getInstance(){    if(instance == null){// 1  instance = new SimpleLazy();// 2  }    return instance;  }}

优点

实现了按需加载

缺点

线程不安全,当 A 线程在进行到 2 时,若有线程 B 进入 1 ,此时 instance 对象还没初始化完,所以 B 线程也会执行 2 ,此时就会同时存在两个 Lazy_1 对象了。

实现二

public class DoubleLock {  private static DoubleLock singleton; private DoubleLock() {  }  public static DoubleLock getInstance() {    if (singleton == null) {// 1      synchronized (DoubleLock.class) {// 2        if (singleton == null) {// 3          singleton = new DoubleLock(); // 4  }      }    }    return singleton;  }}

假设线程 A、B同时进入 1 , 线程 A 获取 2 中的锁,进入 3 ,因为还未初始化所以执行 4 ,最后返回并释放锁,线程 B 获取 2 中的锁,进入 3 ,此时再次判断发现已经创建对象了,直接返回对象实例。

优点

在加锁前进行多一次判断,进而提升代码效率。

缺点

线程并非绝对安全;不能防止反序列化,反射产生新的实例。

在多线程环境中,由于编译器只保证程序执行结果与源代码相同,但是不保证实际指令的执行顺序与源代码相同。所以会出现在多线程环境下调用 getInstance() 方法时会产生乱序的问题。这就是编译器对指令做了 指令重排序优化 造成的。而 volatile 关键字的作用则是通知编译器不执行 指令重排序优化。

singleton = new Singleton(); 是一条简单的初始化并赋值的操作,但是它不是一个原子操作。对于编译器来说,执行该行代码大致需要做三件事情:
A:为对象分配内存;
B:调用 Singleton 的构造函数初始化对象;
C: 将引用 singleton 指向新分配的内存空间

那么,操作 B 依赖于 操作 A,操作 C 依赖于操作 A,对于便一起来说,操作 B 与操作 C 是相互独立的,是可以被重新优化排序的。

当执行顺序为 ACB 时,在执行完 A,C 时,时间片给了另一个线程,而此时已经分配了地址空间了,instance 不为 null 了。所以后面的进程就直接返回实例,此时就会报错了。

实现三

public class DoubleLockSafe{  private static volatile DoubleLockSafe singleton; private DoubleLockSafe() {  }  public static DoubleLockSafe getInstance() {    if (singleton == null) {      synchronized (DoubleLockSafe.class) {        if (singleton == null) {          singleton = new DoubleLockSafe();  }      }    }    return singleton;  }}

这里添加了关键字 volatile 通知编译器不要重新优化排序

优点

绝对线程安全

缺点

不能防止反序列化,反射产生新的实例

实现四

public class StaticInnerClass {  private static class HolderClass {    private static StaticInnerClass lazy = new StaticInnerClass();  }  public static StaticInnerClass getInstance(){    return HolderClass.lazy;  }}

这里采用的是利用静态内部类持有外部类的实例,这样就可以避免饿汉式中不能按需加载的问题,又能减少代码的使用。

优点

绝对线程安全,结构简单

缺点

不能防止反序列化,反射产生新的实例

内存泄漏

在 Android 中很多类的实例化是要依赖 Context 。如果我们把这些类单例化(如,Toast),这时,我们应该传入 Application 的 Context 而不是 Activity 的 Conext ,因为,静态类的生命周期是很长的,程序没退出,它所占的内存也不会释放,如果,我们这时传入的是 Activity 的 Context,那么由于 Activity 的实例被静态类所持有,即使 Activity 被销毁,但是它的引用还存在于单例类中这样 Activity 所占用的内存就不会被释放,而已经不再使用的对象仍占用内存,就会造成内存利用率降低,内存泄漏。

0 0
原创粉丝点击