跳到主要内容

设计模式之单例模式

单例模式定义

单例模式(Singleton Pattern)是指在程序运行期间,保证某个类仅实例化一个或有限个对象。如果某个类的创建和销毁成本很大,或者需要减少对某种资源的占用时,便可以使用单例模式。例如频繁通信的客户端和服务器之间,建立和销毁连接的成本是很大的,对整个程序的性能影响也是严重的。在这种情况下,应当确保整个运行期间仅建立一次连接,也就是仅实例化一次。

单例模式的几种实现

实现单例模式应当注意以下几点:

  1. 要确保全局仅有一个实例。
  2. 单例类自己实例化自己,对外屏蔽实例化方法。
  3. 确保所有使用者均得到同一个实例。

第一种实现:

public class Singleton {
// 确保全局仅有一个实例
private static Singleton instance;

// 对外屏蔽构造函数,避免外部实例化,确保自己创建自己
private Singleton() {}

// 确保使用者均得到一个实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

测试:

public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
}

结果:

Singleton@1b6d3586
Singleton@1b6d3586

可以看到,我们的singleton1和singleton2均访问的同一个对象。

但以上代码存在一个隐患。假设单例类的实例化过程是一个耗时的操作,并且我们在处于一个多线程的环境中。那么当一个线程等待对象实例化时,另一个线程同样进入了临界区,这便使得对象被重复实例化了。而我们只希望单例类只在第一次访问时被实例化。因此,对多线程环境下的单例模式需要进一步的修改。

第二种实现:

public class Singleton {
// 确保全局仅有一个实例
private volatile static Singleton instance;

// 对外屏蔽构造函数,避免外部实例化,确保自己创建自己
private Singleton() {}

// 确保使用者均得到一个实例
public static Singleton getInstance() {
// 确保某一时刻仅一个线程进入,只有第一个线程会实例化对象
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}

相比第一种实现,第二种实现解决了重复实例化对象的问题。但值得注意的是,当我们使用多线程时,往往是为了追求更好的性能。因此,对于多线程锁的使用应当谨慎。第二种实现中,虽然解决重复实例化的问题,却引出了新的问题,即完成实例化对象后,所有线程仍然会互斥访问。因此,需要对实现进一步修改,确保只在没有完成实例化的时候才互斥访问。

第三种实现:

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;
}
}

第三种方式为双重锁定(Double-Check Locking)。现在,我们只会在没有实例的情况下才会阻塞其他的线程。一旦对象被实例化后,便不会再阻塞任何线程。除以上方式外,还有其他的实现方式。

第四种实现:

public class Singleton {
// 确保全局仅有一个实例
private static Singleton instance;

// 对外屏蔽构造函数,避免外部实例化,确保自己创建自己
private Singleton() {}

// 确保使用者均得到一个实例
public static Singleton getInstance() {
return instance;
}
}

这种方式利用类的加载机制来避免多线程的风险,但单例类何时被实例化是难以预料的。如果单例类的实例化依赖于其他的先决条件,可能会发生不可控的行为。