JAVA中的单例实现

  有时候,我们需要确保一个类只有一个实例的时候,我们就会用到单例模式。

实现单例有以下几个关键点:

  1. 构造函数不对外开放(private)。
  2. 通过一个静态方法或枚举返回实例。
  3. 保证单例对象有且只有一个。
  4. 确保反序列化时不会重新创建对象。

懒汉模式(Double Check Lock实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private volatile static Singleton sInstance = null;

private Singleton() { }

public static Singleton getInstance() {
if(sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}

DCL的懒汉模式,既能够在需要的时候初始化,又能保证线程安全,而且单例对象初始化后调用getInstance()不进行同步锁。当然要注意volatile这个关键字只有JDK1.5或之后才对DCL的实现有意义。在第一次执行getInstance()时单例对象才会被实例化,但第一次加载反应稍慢。也由于Java内存模型的原因会偶尔失败,虽然发生概率很小。

饿汉模式

1
2
3
4
5
6
7
8
9
public class Singleton {
private static final Singleton INSTANCE = new Singleton();

private Singleton() { }

public static Singleton getInstance() {
return INSTANCE;
}
}

实现起来非常简单,在初始化静态变量INSTANCE的时候就创建一个实例,在第一次用到这个类的时候会被调用,如果类里面有其他静态方法的话,可能会过早的创建实例,从而降低内存的使用效率。

静态内部类单例模式

所以把饿汉模式稍微改一下,加一个私有的静态内部类作为Holder,这样就只有在第一次getInstance()的时候才会创建实例了,不但线程安全,而且延迟了单例的实例化。

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private Singleton() { }

public static Singleton getInstance() {
return SingletonHolder.sInstance;
}

private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}

枚举单例

1
2
3
public enum Singleton {
INSTANCE;
}

看过《Effective Java》应该对这种单例模式不陌生了,第一次看到这种实现还真是眼前一亮,枚举能有字段还能有方法,最重要的是枚举实例的创建默认是线程安全的,并且在任何情况下都是单例。

注意

在开头我们提到了实现单例的4个关键点,最后一点是”确保反序列化时不会重新创建对象。”,在上面所有的单例实现中,我们都要添加readResolve()来控制对象的反序列化,让他们直接返回单例对象实例。

1
2
3
private Object readResolve() throws ObjectStreamException {
return sInstance;
}

使用容器实现管理单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<>();

private SingletonManager() { }

public static void registerSingleton(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}

public static Object getSingleton() {
return objMap.get(key);
}
}

这是一个比较另类的实现,在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key来获取对象。这种方式可以管理多种类型的单例,并且在使用的时候可以通过统一的接口进行获取操作。

总结

不管用哪种方式实现单例,其核心都是构造函数私有化、通过静态方法获取唯一的实例、保证线程安全和防止反序列化导致重新生成实例对象,按照需求和条件选择适合单例的实现。

#EOF