单例模式是一种常用的软件设计模式,在这种模式中,某个类的实例化次数被限制为一个。意味着,在整个应用程序中,一个类只有一个实例存在。这用于那些我们只需要一个实例来控制操作的情况下,比如:日志、驱动对象、数据库操作等。
首先,让我们来看看一个最简单的单例模式的实现:
public class Singleton {
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private Singleton() {}
}
在这个代码中,我们有一个Singleton类,它有一个Singleton类型的私有静态成员变量instance,同时有一个公有的静态方法getInstance。这个getInstance方法是获取Singleton实例的唯一途径。当我们第一次调用getInstance方法时,会创建一个Singleton的实例,并赋值给instance;而在后续的调用中,如果instance已经被创建,则直接返回。也就是说,Singleton类的实例最多只会被创建一次。
把构造函数设为私有的是非常关键的,这是为了防止在外部被new出新的实例。并且我们注意到,instance的创建过程被synchronized修饰,这是为了防止在多线程环境下生成多个实例。然后,是另外一种更为推荐的单例模式的实现方法,那就是使用Java的内部类来保证单例:
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
在这个实现中,SingletonHolder是一个私有的静态内部类,当第一次加载Singleton类时不会初始化这个静态内部类,只有在第一次调用Singleton的getInstance方法时才会导致SingletonHolder被初始化。由于实例的建立是在类加载的时候完成,这样就可以确保线程安全。这个模式的优点是既保证了线程安全,又能做到延迟加载。
除了前面提到的最基础的单例模式,有很多种使用单例模式的方式,比如"懒汉式","饿汉式","双检锁/双重校验锁 (DCL,即 double-checked locking)","登记式/静态内部类","枚举"等。其中,"饿汉式"和"懒汉式"是最常用的两种形式。
"饿汉式"是最常见的一种实现方式,这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快,它是一种典型的以时间换空间的方式。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
相对应的,"懒汉式"是另一种方式,较之前示例不同,懒汉式是典型的空间换时间的方式,只有在使用的时候才去创建对象,可以节约内存。
public class Singleton {
private volatile static Singleton singleton ;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
然后,还有一种方式是双检锁/双重校验锁(DCL,即 double-checked locking)。这种方式在加锁的情况下,我们再检查一次,如果单例仍然不是空,就返回实例,否则就创建一个新的实例。这是一种比较复杂的实现方式,但可以在保持线程安全的前提下,提高了单例的获取性能。
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;
}
}
在上述的代码中,第一次检查是为了不必要的同步,第二次检查则是再同步情况下进行,保证了只生成一个实例。
理解了Java中单例模式的原理和实现后,你就可以在自己的代码中灵活应用这种设计模式了。例如在应用日志、数据库连接池等需要全局唯一的资源的管理时就可以使用到。这也是你作为Java开发者必会的面试题之一。