单件模式:确保一个类只有一个实例,并提供一个全局访问点。
单件模式是一个很简单的设计模式,在开发过程中有一些对象其实我们只需要一个,比如线程池,缓存,日志对象等等。这类对象如果制造多个实例,就会导致许多问题产生,比如程序行为异常,资源使用过度,全局配置不一致等等。
我们知道,创建一个只有单个实例的对象,可以通过 static final
关键字修饰来达到,并且具有全局访问点,那为什么还需要单件模式呢?其实使用java静态变量有一些缺点,比如必须在程序一开始就创建好对象,如果这个对象非常耗费资源,并且一直没有用到它,就会造成浪费。这并不是说不能使用静态变量来实现功能,而是需要具体情况具体分析。
单件模式的经典实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package singleton;
public class Singleton {
/**
* 用一个静态变量记录Singleton类的唯一实例
*/
private static Singleton singletonInstance;
/**
* 把构造方法声明为私有的,只能Singleton内部才能调用,不能外部 new 一个实例
*/
private Singleton(){
}
/**
* 实例化唯一对象,并返回这个实例
* @return
*/
public static Singleton getInstance(){
if (singletonInstance == null){
singletonInstance = new Singleton();
}
return singletonInstance;
}
// Singleton 的其他变量和方法 ...
}
经典单件模式通过 getInstance
方法来确保产生得到唯一实例,并且是延迟加载,只有在第一次使用的时候才会去创建这个实例。
看起来经典实现没有什么问题,实际上在多线程模式下会出现问题,没法保证只创建了一个实例,如果有两个线程同时第一次调用 getInstance
方法,那么就有可能同时判断 singletonInstance
为空,接着同时创建了两个实例,那么程序就会出现各种问题。
处理多线程
上面的问题只需要在 getInstance
方法上加 synchronized
关键字,变成同步方法就可以解决问题了,同步方法确保了同一时间只能有一个线程能进入方法。但是有一点不好,那就是同步会降低性能,而且更严重的是:只有第一次调用 getInstance
方法时才会需要同步,如果要很频繁的调用方法,则显然是一种浪费。
怎么样改变这种情况呢?书上给出了三种选择:
-
如果对同步方法造成的性能降低没有影响,就使用
synchronized
,既简单又有效。 -
使用“急切”创建实例,而不是延迟实例化的做法
如果程序必定会创建并使用单件实例,或者在创建和运行时的负担不太繁重,可以一开始就创建好实例,就像使用静态变量一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
package singleton; public class SingletonInit { private static SingletonInit singletonInstance = new SingletonInit(); private SingletonInit(){ } public static SingletonInit getInstance(){ return singletonInstance; } // SingletonInit 的其他变量和方法 ... }
-
用“双重检查加锁”在
getInstance
中减少使用同步首先检查实例是否已经创建了,如果没有创建,才进行同步,这样一来,只有第一次会同步,就不会有性能问题了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
package singleton; public class SingletonDoubleChecked { /** * volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。 */ private volatile static SingletonDoubleChecked instance; private SingletonDoubleChecked(){} public static SingletonDoubleChecked getInstance(){ if (instance == null){ // 只有第一次才会执行 synchronized (SingletonDoubleChecked.class){ // 进入同步区块后再次判断是否已被创建 if (instance == null){ instance = new SingletonDoubleChecked(); } } } return instance; } }
这里得注意
volatile
这个关键字,在Java内存模型中,有主存,每个线程也有自己的工作内存 (例如寄存器)。为了性能,一个线程会在自己的内存中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的内存中的值可能与另外一个线程内存中的值,或者主存中的值不一致的情况。 一个变量声明为volatile
,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程内存中。
以上就是书中关于单件模式的全部内容了。