设计模式——单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
单例模式在实际的开发过程中,使用最常见。针对频繁使用的类,我们可以将它定义为一个单例来避免类对象频繁的创建销毁,提高效率。单例模式有以下特点:
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类给其它对象提供这一实例
单例模式的实现方式
围绕单例模式的特点,一般常见的实现方式有以下几种:
- 懒汉式
- 饿汉式
- Double Check Lock(DCL)双重检查锁定
- 静态内部类实现
- 枚举单例
- 记录式单例
懒汉式
懒汉式是先声明一个静态类对象,然后在用户第一次调用初始化方法的时候进行初始化。
懒汉式线程非安全
这种模式的缺陷就是无法在多线程条件下保证实例只有一个。所以严格意义上来说,在多线程环境下就不能称之为单例模式了。
/***
* 懒汉式
* @author Iflytek_dsw
*
*/
public class Singleton {
private static Singleton instance;
private Singleton(){};
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
懒汉式线程安全
通过对getInstance方法添加同步方法锁的形式来达到线程同步,每次调用getInstance方法都会进行同步,这样在单线程环境下效率很低。
/***
* 懒汉式线程安全
* @author Iflytek_dsw
*
*/
public class Singleton {
private static Singleton instance;
private Singleton(){};
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
饿汉式
在单例对象声明的时候即进行实例化。
/***
* 饿汉式
* @author Iflytek_dsw
*
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){};
public static synchronized Singleton getInstance(){
return instance;
}
}
这种方式避免了在多线程环境下执行getInstance的问题,但是在不同的类加载器的环境下也可能出现不同的实例。
DCL模式(双重检查锁定)
双重检查锁定是通过多次判断instance是否为null来实现,然后结合volatile关键字来实现。
/***
* DCL模式
* @author Iflytek_dsw
*
*/
public class Singleton {
private volatile static Singleton instance;
private Singleton(){};
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查锁定模式实现的亮点是在getInstance方法的实现以及实例的定义上。它通过两次判断null值进行实现。在第一次判断主要是为了不必要的锁机制,第二次判断是为了确保在null的情况下进行创建实例。这里使用volatile关键字来定义instance就是确保能够保持一致性。DCL模式资源利用率高,只有在第一次调用getInstance时才会继续拧初始化操作。
静态内部类
/***
* 静态内部类模式
* @author Iflytek_dsw
*
*/
public class Singleton {
private Singleton(){};
public static synchronized Singleton getInstance(){
return SingleBuilder.instance;
}
private static class SingleBuilder{
private static final Singleton instance = new Singleton();
}
}
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,即使Singleton类被装载了,instance不一定被初始化,因为SingleBuilder类没有被主动使用,只有显示通过调用使用实例时,才会显示装载SingleBuilder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的,所非常推荐这种方式。
枚举单例
枚举的每个实例在Java堆中有且只有一个副本,这种特性让枚举很容易就实现了单例模式。
enum SingletonEnum{
INSTANCE;
public void drawCircle(){
System.out.print("drawCircle");
}
}
public static void main(String[] args) {
SingletonEnum.INSTANCE.drawCircle();
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,非常推荐使用。枚举在Java中和普通类一样,可以拥有自己的字段和方法,最重要的是枚举的实例创建是线程安全的,任何情况下都是一个单例。
记录式单例
记录式单例就是通过自定义管理器然后记录管理实例的创建创建,以此来保证单例。
/***
* 记录式单例
* @author Iflytek_dsw
*
*/
public class Singleton {
private static Map<String,Object> keyMap = new HashMap<>();
private Singleton(){};
public static Singleton getInstance(String key){
return (Singleton) keyMap.get(key);
}
public static void registerSingle(String clazzName,Object obj){
if(keyMap.get(clazzName) == null){
keyMap.put(clazzName, obj);
}
}
}
记录式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。
总结
不管以哪种方式实现单例模式,核心都是私有化构造函数,并且通过一个静态方法获取单个实例,这个过程中出现的变种就是要保证线程是否安全、放置序列化导致新生成对象的问题。选择哪种方式取决于使用环境:是否是多线程环境、性能要求、JDK版本等。