单例模式是众多设计模式的一种。单例类可以保证其类型只会生成一个实例,只拥有一个实例在很多时候是很有用的,比如说全局访问以及缓存代价高昂的资源;不过如果在多线程环境下使用单例,那就可能引入一些竞态条件问题。由于大多数编程语言并没有提供创建单例的内置机制,因此需要开发者自己来实现。
1 单例概述
单例模式用于确保一个类只有一个实例,并且提供了实例的一个全局访问点。该模式通常与工厂模式配合使用。
单例模式的一般应用场景:
1)跨越整个应用程序域来访问共享数据,比如配置数据。
2)只加载并缓存代价高昂的资源一次,这样就可以做到全局共享访问并改进性能。
3)创建应用日志实例,因为通常情况下只需要一个即可。
4)管理实现了工厂模式的类中的对象。
5)延迟创建静态类,单例可以做到延迟实例化。
延伸:Spring在创建Bean时使用了单例(默认情况下,SpringBean是单例的),在JavaEE内部会使用单例,比如在服务定位器中。javaSE也在Runtime类的实现中使用了单例模式。
不过过度的使用单例模式意味着不必要的资源缓存,无法让垃圾回收器回收对象并释放宝贵的内存资源。当然这样做也无法利用对象创建和继承的好处。大量使用单例类可能会导致内存和性能问题。并且单例类的使用对测试并不友好。
2 单例类的实现
2.1 单例类的简单实现
由于单例只有一个实例,因此要控制对象的创建。首先将构造方法进行私有化。接着要提供一个方法创建实例,并且保证该方法是静态的,如果实例已经存在,则返回它。
package com.hzw.singleton;
public class MySingleton{
private static MySingleton instance;
private MySingleton(){}
public static MySingleton getInstance(){
if(instance==null){
instance = new MySingleton();
}
return instance;
}
}
getInstance()方法首先会判断单例有没有创建,如果没有创建则创建它,否则,返回之前调用getInstance()方法创建的实例。后续的每次调用都是返回之前创建的MySingleton对象的实例,这段实现单例模式的代码看起来可以正常运行,不过在实际上却是存有BUG的而且是不完整的。由于对象的创建方法并不是原子的,因此在竞态条件下容易出错。在多线程环境下,会导致创建多个单例实例的后果。
2.2 同步单例保证线程安全或者在类加载时初始化单例类实例
要解决上面产生的竞态问题,需要在出现该问题的地方采用加锁机制。并且在实例返回后再释放锁。在java中使用synchronized关键字实现锁机制。
package com.hzw.singleton;
public class MySingleton{
private static MySingleton instance;
private MySingleton(){}
public static synchronized MySingleton getInstance(){
if(instance==null){
instance = new MySingleton();
}
return instance;
}
}
或者在加载类的同时创建单例实例,这样就不必同步单例实例的创建,并在JVM加载完所有类时就创建好单例对象(这是在类调用getInstance()方法之前发生的)。之所以可以这样,是因为静态成员与静态块是在类加载时执行的。
package com.hzw.singleton;
public class MySingleton{
private static final MySingleton instance = new MySingleton();
private MySingleton(){}
public static MySingleton getInstance(){
return instance;
}
}
还可以使用静态块,不过这会导致延迟初始化,因为静态块是在构造方法调用之前执行的。
package com.hzw.singleton;
public class MySingleton{
private static MySingleton instance = null;
static{
instance = new MySingleton();
}
private MySingleton(){}
public static MySingleton getInstance(){
return instance;
}
}
2.3 使用双重检测锁
双重检测锁是一种流行的创建单例的机制,它比其他方法更加安全,因为它会在锁定单例类之前检查一次单例的创建,在对象创建前再一次检查。
package com.hzw.singleton;
public class MySingleton{
private volatile MySingleton instance;
private MySingleton(){}
public static MySingleton getInstance(){
if(instance==null){
synchronized(MySingleton.class){
if(instance==null){
instance = new MySingleton();
}
}
}
return instance;
}
}
getInstance()方法会在创建前检查私有的MySingleton实例成员两次,看它是不是null,并赋值为一个MySingleton实例。
2.4 使用枚举机制创建单例实例
以上方法都不是绝对安全的。比如说,开发者可以通过Java Reflection API将构造方法的访问权限改为public,这样就可以再次创建单例了。
在java中,创建单例的最佳方式是使用java5中引入的枚举类型,这也是Effective Java中极力推荐的方式。因为枚举类型本质上就是单例的,因此JVM会处理创建单例所需的大部分工作。这样通过使用枚举类型,就不需要在处理同步对象创建(单例实例的创建)和提供(获取创建的单例实例)等工作了,还能避免与初始化相关的问题。
package com.hzw.singleton;
public enum MySingletonEnum{
INSTANCE;
public void method1(){}
public void method2(){}
....................
}
在该例中,对单例对象实例的引用是通过如下方式获得的:
MySingletonEnum mse = MySingletonEnum.INSTANCE;
一旦拥有了单例的引用,就可以调用它的方法:例如 mse.method1();