【0】README
0.1)本文部分描述转自 “head first 设计模式”, 旨在学习 单件模式(单例模式) 的相关知识 及其应用;
【1】单件模式
1.0)单件模式的应用背景:有一些对象其实我们只需要一个,比方说: 线程池,缓存,对话框,注册表等的对象,这都可以通过单件模式来解决;
1.1)定义:确保一个类只有一个实例,并提供一个全局访问点;
1.2)全局变量的缺点:如果将对象赋值给一个全局变量,那么你必须在程序一开始就创建好对象,对吧?万一这个对象非常耗费资源,而程序在这次的执行过程中又一直没有用到它,不就形成浪费了嘛?
(干货——使用全局变量可能出现的问题)
【2】剖析经典的单件模式实现
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// other useful methods here
public String getDescription() {
return "I'm a classic Singleton!";
}
}
对以上的代码的分析(Analysis):
A1)如果该对象不存在,我们就利用私有构造器产生一个 Singleton 实例并把它赋值到 uniqueInstance 静态变量中。A2)注意:如果我们不需要这个实例,它就永远不会产生。这就是“延迟实例化”; (干货——延迟实例化)A3)看看它的类图: getInstance()方法是静态的,这意味着它是一个类方法,所以可以在代码的任何地方使用 Singleton.getInstance() 访问它。这和访问全局变量一样简单,只是多了一个优点: 单件可以延迟实例化;
【3】并发访问实例方法(处理多线程)
3.0)多线程访问实例方法所遇到的问题:返回了两个不同对象object1 和 object2,多线程访问的细粒度steps 如下所示:
3.1)把 getInstance() 变成同步(synchronized)方法,解决并发问题;
public class ConcurrencySingleton {
private static ConcurrencySingleton uniqueInstance;
private ConcurrencySingleton() {}
public static synchronized ConcurrencySingleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new ConcurrencySingleton();
}
return uniqueInstance;
}
// other useful methods here
public String getDescription() {
return "I'm a classic Singleton!";
}
}
3.2)加上 synchronized后的性能问题:显然,这样会降低同步的性能,这引入了另一个问题;
问题详述:这个问题比你想象的还要严重,因为只有第一次执行此方法时,才真正需要同步。换句话说,一旦设置好uniqueInstanc 变量, 就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘,显著地降低了程序性能;(因为当多个线程并发访问 getInstance 方法的时候,有且只有一个线程能够获得同步锁,访问方法成功,某个线程访问成功后,其他线程才有可能去访问该方法,此时叫串行访问而不是并行访问了);
3.3)solution(多线程下的单件模式):
s1)如果getInstance() 的性能对应用程序不是很关键,就什么也别做;(不用加 synchronized关键字);s2)使用 急切创建实例,而不用延迟实例化的做法; (干货——比较急切实例化和延迟实例化的区别)![]()
public class Singleton { private static Singleton uniqueInstance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return uniqueInstance; }
s3)使用双重检查加锁,在 getInstance()中减少使用同步:利用双重检查加锁,首先检查是否实例对象已经创建了,如果没有创建,才进行同步。这样一来,也就只有第一次才会同步,这正是我们想要的; (干货——我个人推荐使用这个加锁机制)![]()
![]()
public class ConcurrencySingletonV2 { private volatile static ConcurrencySingletonV2 uniqueInstance; private ConcurrencySingletonV2() {} // 只有第一次才执行全部代码,否则跳转到 return 语句行 public static ConcurrencySingletonV2 getInstance() { if (uniqueInstance == null) { // 第一次检查 synchronized (ConcurrencySingletonV2.class) { if (uniqueInstance == null) { // 第二次检查:进入同步块后,如果实例仍然是null,才创建实例 uniqueInstance = new ConcurrencySingletonV2(); } } } return uniqueInstance; } }
Attention)Volatile 关键字:为实例域的同步访问提供了一种免锁机制, 如果说明一个域为 volatile, 那么编译器和 虚拟机就知道该域是可能被另一个线程并发更新的;