你真的会写单例模式吗?

在我们开发的 Android 项目中,单例模式可能是我们用的最多的开发模式了,但是对于这种常用的开发模式,我们真的用对了吗?本篇博文我们就来探讨一下单例模式。

前言

在讨论之前,我们先来看一下新手使用单例模式,最常见的代码,如下:

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

private static Singleton sInstance = null;

public static Singleton getInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}

上面的单例代码咋一看上去没什么问题,其实问题很大,真的很大。这并不是真的单例。

什么是单例?

简单来说,就是在程序的运行过程中,只保持一个实例对象。

为什么要用单例?

在我们开发项目的过程中,某些类存在占用资源较大,每次 new 的时候性能消耗较大,这个时候我们应考虑使用单例来提高性能。

使用单例要注意什么?

主要有以下三点:

  • 1.私有化构造器;
  • 2.定义静态的单例 instance 对象 和 getInstance() 方法;
  • 3.getInstance() 方法中要使用同步锁 synchronized(Singleton.class) 防止多线程多次示例化。

实践

在我们写正确的 单例 代码前,我们先来测试以下博文开头给出的这种单例 demo 为何不对;

错误点有以下几方面:

  • 1.未私有化构造器,这样就导致了调用者可能越过 getInstance() 方法,直接调用默认的公有的构造器来 new 对象。
  • 2.getInstance() 方法未加锁,多线程调用时,可能多次示例化对象。

所谓口说无凭,我们看下面的测试代码,来实际验证以下是否真的存在多次实例化对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final CountDownLatch latch = new CountDownLatch(1);
int threadCount = 1000;

for (int i = 0; i < threadCount; i++) {
new Thread(new Runnable() {

@Override
public void run() {
try {
// 让所有的线程进入等待状态
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}

// 测试示例 hashCode
Log.d("Test", String.valueOf(Singleton.getInstance().hashCode()));
}
}).start();
}

// 释放锁,让所有的线程并发运行(所谓并发,也是伪并发,相信知道 CPU 运行原理的同学都会知道)
latch.countDown();

查看 Log:

我们明显和可以看到示例对象的 hashCode 有两个不同的值,也就是说对象在多线程的情况下,被实例化了两次。

接下来是正确的单例代码 demo:

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

private static Singleton sInstance = null;

// private constructor suppresses
private Singleton() {}

public static Singleton getInstance() {
// if already inited, no need to get lock everytime
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}

}

需要注意的是:

  • 1.在 getInstance() 方法中有两次判空,第一次判空是为了防止 sInstance 已经实例化后,再次获取锁对象。这样做可以提高性能。
  • 2.很多人都会重新 new Object() 来充当 synchronized 锁对象,完全没有必要新建一个对象,我们直接用单列对象本身即可,防止消耗不必要的内存。
文章有帮助到您?不妨打赏博主一碗拉面或一杯咖啡的小费吧 :-D!