You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
6.0 KiB
236 lines
6.0 KiB
---
|
|
单例模式
|
|
---
|
|
|
|
#### 目录
|
|
|
|
1. 思维导图
|
|
2. 定义和使用场景
|
|
3. 实现方式
|
|
4. 反序列化和反射
|
|
5. 优缺点
|
|
6. 应用
|
|
|
|
#### 思维导图
|
|
|
|
![](https://i.loli.net/2019/01/06/5c316f61d1c06.png)
|
|
|
|
#### 定义和使用场景
|
|
|
|
定义:一个类只允许创建一个对象,那么这个类就是一个单例类。
|
|
|
|
使用场景:
|
|
|
|
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源。除此之外,我们还可以使用单例解决资源访问冲突的问题。
|
|
|
|
#### 实现方式
|
|
|
|
##### 懒汉式
|
|
|
|
```java
|
|
public class Singleton {
|
|
|
|
private static Singleton singleton;
|
|
|
|
private Singleton() {
|
|
|
|
}
|
|
|
|
public static synchronized Singleton getInstance() {
|
|
if (singleton == null) {
|
|
singleton = new Singleton();
|
|
}
|
|
return singleton;
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
##### 饿汉式
|
|
|
|
```java
|
|
public class Singleton {
|
|
|
|
private static Singleton singleton = new Singleton();
|
|
|
|
private Singleton() {
|
|
|
|
}
|
|
|
|
public static Singleton getInstance() {
|
|
return singleton;
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
##### DCL
|
|
|
|
```java
|
|
public class Singleton {
|
|
|
|
private static volatile Singleton singleton;
|
|
|
|
private Singleton() {
|
|
|
|
}
|
|
|
|
public Singleton getInstance() {
|
|
if (singleton == null) {
|
|
synchronized (Singleton.class) {
|
|
if (singleton == null) {
|
|
singleton = new Singleton();
|
|
}
|
|
}
|
|
}
|
|
return singleton;
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
在 getInstance 方法里进行了两次判空:第一次判空是为了避免不必要的同步,第二次判空是为了在 null 的情况下创建实例。
|
|
|
|
之所以用 volatile 修饰 mInstance,那是因为 mInstance = new SingleTon() 并不是一个原子操作,它大致做了三件事:
|
|
|
|
1. 给 mInstance 分配内存
|
|
2. 调用其构造方法,初始化成员字段
|
|
3. 将 mInstance 对象指向分配的内存空间(此时 mInstance 就不是 null 了)
|
|
|
|
但是由于 CPU 的优化操作,可能会对这几个操作进行乱序执行,第二条和第三条的操作的执行顺序无法保证。如果线程 A 执行到 getInstance() 方法,进入锁块,先执行 第三条操作,然后另外一个线程 B 也执行到 getInstance() 方法,发现 mInstance 不为空了,就直接取走了 mInstance 对象,在使用时就会出错,这就是 DCL 失效问题。而 volatile 能禁止指令重排序。
|
|
|
|
当然,在高版本不加 volatile 其实也没啥,因为 JVM 已经把 new 作为一个原子操作了。
|
|
|
|
DCL 还有一种优化的写法是:
|
|
|
|
```java
|
|
class Helper {
|
|
private static volatile Helper helper;
|
|
|
|
public static Helper getHelper() {
|
|
Helper localRef = helper;
|
|
if (localRef == null) {
|
|
synchronized (Helper.class) {
|
|
localRef = helper;
|
|
if (localRef == null) {
|
|
helper = localRef = new Helper();
|
|
}
|
|
}
|
|
}
|
|
return localRef;
|
|
}
|
|
// other functions and members...
|
|
}
|
|
```
|
|
|
|
|
|
|
|
##### 静态内部类
|
|
|
|
```java
|
|
public class Singleton {
|
|
|
|
private Singleton() {
|
|
|
|
}
|
|
|
|
public static Singleton getInstance() {
|
|
return SingletonHolder.singleton;
|
|
}
|
|
|
|
private static class SingletonHolder {
|
|
private static Singleton singleton = new Singleton();
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
当第一次加载 SingleTon 类的时候并不会初始化 mInstance,只有在第一次调用 getInstance 方法时才会导致 mInstance 被初始化。因此,第一次调用 getInstance 方法会导致虚拟机加载 SingleHolder 类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,这是推荐使用的单例模式实现方式。
|
|
|
|
类加载的执行 JVM 可以保证是线程安全的。
|
|
|
|
##### 枚举类
|
|
|
|
```java
|
|
public enum SingleTonEnum {
|
|
INSTANCE;
|
|
public void doSomething(){
|
|
|
|
}
|
|
}
|
|
```
|
|
|
|
枚举在 Java 中与普通的类是一样的,不仅能够有字段,还能有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
|
|
|
|
#### 反序列化和反射
|
|
|
|
##### 反序列化
|
|
|
|
对于上面的实现方式,除了枚举,都无法避免反序列化。反序列化创建对象是不走构造函数的,所以构造函数是私有化完全没用,要杜绝单例对象被反序列化时重新创建对象,则必须加入如下方法:
|
|
|
|
```java
|
|
private Object readResove() throws ObjectStreamException {
|
|
return mInstance;
|
|
}
|
|
```
|
|
|
|
##### 反射
|
|
|
|
直接在构造方法中抛异常。
|
|
|
|
```java
|
|
private SingleTon(){
|
|
throw new RuntimeException("SingleTon can't be reflected");
|
|
}
|
|
```
|
|
|
|
#### 优缺点
|
|
|
|
优点:
|
|
|
|
由于只有一个实例,,故可以减少内存开销,避免对资源的多重占用。
|
|
|
|
缺点:
|
|
|
|
单例类扩张困难,职责过重,一定程度上违背 “单一职责原则”。
|
|
|
|
注意单例对象可能造成的内存泄露问题!
|
|
|
|
#### 应用
|
|
|
|
在 Android 中,LayoutInflater 使用到了单例模式。
|
|
|
|
```java
|
|
// ContextImpl
|
|
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
|
|
```
|
|
|
|
```java
|
|
final class SystemServiceRegistry {
|
|
static {
|
|
//...
|
|
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
|
|
new CachedServiceFetcher<LayoutInflater>() {
|
|
@Override
|
|
public LayoutInflater createService(ContextImpl ctx) {
|
|
return new PhoneLayoutInflater(ctx.getOuterContext());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
```java
|
|
public static LayoutInflater from(Context context) {
|
|
LayoutInflater LayoutInflater =
|
|
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
if (LayoutInflater == null) {
|
|
throw new AssertionError("LayoutInflater not found.");
|
|
}
|
|
return LayoutInflater;
|
|
}
|
|
```
|
|
|
|
其实就是缓存到一个 Map 里面进去取。
|
|
|
|
还有一些单例是比较直观的,比如 AccessibilityManager 以及 Java 中的 Runtime 类等等。 |