单件模式
定义:确保一个类只有一个实例,并提供全局访问点。
编写格式:
| 1 2 3 4 5 6 | public class MyClass{ private MyClass(){}//构造方法私有化 public static MyClass getInstance(){ //提供全局访问点 return new MyClass(); } } |
有一些对象其实我们只需要一个,比如线程池、缓存、对话框、处理偏好设置和注册表的对象、日志对象。如果制造出多个实例,就会导致程序的行为异常、资源使用过量,或者不一致的结果等。
单件模式实现1:延迟实例化
懒汉式
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | public class Singleton{ private static Singleton uniqueInstance;//创建一个静态变量来记录Singleton类的唯一实例 //。。。其他实例变量 private Singleton (){}//私有构造方法 //使用getInstance()方法实例化对象 public static Singleton getInstance(){ if(uniqueInstance==null){//判断uniqueInstance是否为空,为空则创建实例 uniqueInstance=new Singleton(); } return uniqueInstance; } //...其余方法 } |
类图:

“延迟实例化”在我们不需要这个实例的时候它将不会产生。
上面的方法的确实现了单件模式,但是在某些情况下将出现一些严重的问题,例如:
在多线程的情况下,可能会创建多个对象。怎么解决多线程单件问题呢?下面有三种方法:
单件模式实现2:通过synchronized关键字解决多线程问题
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Singleton { private static Singleton singleton; private Singleton(){} //synchronized关键字迫使每个线程在进入方法前,要先等候别的线程离开该方法 public static synchronized Singleton getInstance() { if(singleton == null) { singleton = new Singleton(); } return singleton; } } |
同步(synchronized)可以解决多线程存在的问题,但是同步会降低性能,且第一次执行此方法时才真正需要同步,一旦设置好uniqueInstance变量,就不再需要同步这个方法了,之后每次调用这个方法,同步都是一种累赘。
建议:
1.如果getInstance()d的性能对应用程序不是很关键,则可以忽略同步带来的性能下降问题。
2.同步一个方法可能会让程序执行效率下降100倍,如果getInstance()是程序使用在频繁运行的地方则不建议这样使用。
3.如果应用程序总是创建并使用单件实例或创建运行时的负担不太繁重,可以使用“急切实例化”创建实例,而不用“延迟实例化”的做法。
单件模式实现3:急切实例化
饿汉式
| 1 2 3 4 5 6 7 8 | public class Singleton{ //在静态初始化器中创建单件,且保证了线程安全 private static Singleton uniqueInstance=new Singleton(); private Singleton(){} public static Singleton getInstance(){ return uniqueInstance; } } |
利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单件实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。
单件模式实现4:双重检查加锁
使用双重检查加锁(double-checked locking),首先检查实例是否已经创建了,如果尚未创建,才进行同步。这样一来,只有第一次会同步。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //在getInstance()中减少使用同步 //注意:双重检查加锁不适用于java1.4以及之前的版本,因为Volatile关键字会导致双重检查加锁失效 class Singleton { //Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。 //即阻止线程为了优化性能而保有变量的私有拷贝 private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance() { if(singleton == null)//检查实例,如果不存在就进入同步区块 { //注意,只有第一次才彻底执行这里的代码 synchronized(Singleton.class) { if(singleton == null)//进入区块后,再检查一次 { singleton = new Singleton(); } } } return singleton; } } |
其他问题:
在Java1.2之前,垃圾收集器有个bug,会造成当单件在没有全局的引用时被当作垃级清除。
多个类加载器(class loader)可能会导致单件失效。
单件模式要点:
- 单件模式确保程序中一个类最多只有一个实例。
- 单件模式也提供访问这个实例的全局点。
- 在Java中实现单件模式需要私有的构造器、一个静态方法和一个静态变量。
- 确定在性能和资源上的限制,然后小心地选择适当的方案来实现单件,以解决多线程的问题(我们必须认定所有的程序都是多线程的)。
- 如果不是采用第五版的Java 2,双重检查加锁实现会失效。
- 小心,你如果使用多个类加载器,可能导致单件失效而产生多个实例。
- 如果使用JVM1.2或之前的版本,你必须建立单件注册表,以免垃圾收集器将单件回收。