设计模式之单例模式

Android中单例模式整理记录

  • 什么是单例模式

使用时,单例模式的对象只有一个实例存在,不允许自由的构造对象

  • 单例模式使用场景

确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例

… …

  • 如何使用单例
  1. 私有构造函数,禁止外部使用构造函数创建对象
  2. 通过一个静态方法或者枚举来提供返回单例实例
  3. 确保单例类的对象在反序列化的时候不会重新构建对象
  4. 确保在多线程下,也只有一个实例
  • 单例模式分类

1,饿汉式

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton instance = new Singleton();

private Singleton() {
}

private Singleton getInstance() {
return instance;
}

}

2,懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private static Singleton instance ;

private Singleton() {
}

private synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

饿汉式和懒汉式的区别

饿汉式:在声明静态对象的时候就初始化

懒汉式:声明一个静态变量,并且在用户第一次调用getInstance的时候进行初始化

​ 优点:单例在使用的时候才会初始化,一定程度上节约了资源

​ 缺点:第一次加载的时候会初始化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成了不必要的同步开销,一般不介意这么用。

3,Double check lock(DCL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private static Singleton instance;

private Singleton() {
}

private synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}

DCL :

介绍DCL 在getInstance 方法中 对instance 进行两次判空:相信很多人对此都有些疑惑。为什么要判断两次,第一个判空是为了避免不必要的同步,第二层判断是为了在null 情况下创建实例。instance=new Singleton(); 语句看起来是有代码,单实际是一个原子操作,最终会被编译成多条汇编指令,大致做了三件事:

1.给Singleton 分配内存

2.调用Singleton 的构造函数,初始化成员字段

3.将instance 对象指向分配的内存空间(此时instance 就不是null 了)但是jdk 1.5 以后java 编译器允许乱序执行 。所以执行顺序可能是1-3-2 或者 1-2-3.如果是前者先执行3 的话 切换到其他线程,instance 此时 已经是非空了,此线程就会直接取走instance ,直接使用,这样就回出错。DCL 失效。解决方法 SUN 官方已经给我们了。将instance 定义成 privatevolatilestatic Singleton instance =null:

即可DCL 的优点,资源利用率高,第一次执行getInstance 时才会被实例化,效率高。缺点:第一次加载反应慢,也由于java 内存 模型的原因偶尔会失败,在高并发环境下,有一定缺陷,虽然发生概率很小。(很常用)

4.静态内部类单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {

private Singleton() {
}

private synchronized Singleton getInstance() {
return SingletonHolder.instance;
}

private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}

加载singleton 类时不会初始化instance 只有在调用getInstance 方法时,才会导致instance 被初始化,这个方法不仅能够确保线程安全,也能够保证 单例对象的唯一性,同时也延迟了单例的实例化,是推荐使用的单例模式实现方式。

5,防止反序列化过程中创建多个对象

加入下列方法

1
2
3
protected Singleton readResolve() throws ObjectStreamException {
return SingletonHolder.instance;
}

而当实现了readResolve方法后,jvm就会有readResolve返回指定对象,也就保证了单例性。实验证明,如果没有声明readResolve方法,存入对象的hashcode和取出对象的hashcode不一致。

推荐下列方式(如果需要序列化的话):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton implements Serializable {

private Singleton() {
}

private synchronized Singleton getInstance() {
return SingletonHolder.instance;
}

private static class SingletonHolder {
private static volatile final Singleton instance = new Singleton();
}

protected Singleton readResolve() throws ObjectStreamException {
return SingletonHolder.instance;
}
}

6,volatile关键字的使用

当一个变量定义为 volatile 之后,将具备两种特性:

  1.保证此变量对所有的线程的可见性,这里的“可见性”,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

  2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile 性能:

  volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

7,Android源码或者框架使用

  • ImageLoader
  • EventBus
  • InputMethodManager
  • AccessibilityManager
  • ActivityManager(自定义关闭所有Activity的工具类)
文章目录
  1. 1. 1,饿汉式
  2. 2. 2,懒汉式
  3. 3. 3,Double check lock(DCL)
  4. 4. 4.静态内部类单例模式
  5. 5. 5,防止反序列化过程中创建多个对象
  6. 6. 6,volatile关键字的使用
  7. 7. 7,Android源码或者框架使用