探究单例模式(java)
单例模式的作用就是保证在整个系统中,某一个类的实例只有一个。单例模式可以避免产生多个对象而消耗过多的资源。比如访问数据库资源的时候,可以使用单例模式。
创建单例模式,要满足两个条件:
- 构造函数必须是private。
- 通过静态方法可以获取到单例类对象。
一般有以下几种方法来创建。
一、饿汉模式
public class Singleton {
private static final Singleton instance = new Singleton();
// 构造函数私有
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
饿汉模式在声明静态对象时就已经实例化了。
二、懒汉模式
public class Singleton {
private Singleton() {}
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉模式只有在使用时会实例化,在一定程度上节约了资源。但是第一次加载时需要及时进行实例化,反应慢。而且当多个线程调用getInstance()方法时,由于该方法是同步方法,因此只有一个线程能够获得锁,其他线程必须等待,造成了不必要的同步开销。
三、Double CheckLock
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Double CheckLock进行了两层判空操作。第一层是为了避免不必要的同步。当instance不为空时,可以不需要同步,直接拿到instance对象。第二层判空是为了在instance为null时,加入了同步锁,保证instance只实例化一次。
DCL没有了懒汉模式的同步开销。但是仍然有个小缺陷。
java在执行instance = new Singleton();时,将这段代码编译成了多条汇编指令:
- 给Singleton实例分配内存空间。
- 调用Singleton()构造函数,初始化成员字段。
- 将instance对象指向分配的内存空间。
问题是后两条语句不是按顺序执行的,有可能先执行第二条,也有可能先执行第三条。当先执行第三条时,instance就不为空了,这时如果一个线程取走instance进行使用,由于没有初始化成员字段,导致程序出错。
为了避免这个问题,可以使用volatile关键字,它保证instance每次都从主内存读取。改进后的代码如下:
public class Singleton {
// 加入volatile关键字
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;
}
}
四、静态内部类的方式
在网上看到一种创建单例模式的方法:
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
// 静态内部类里实例化Singleton
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
这种方法借鉴了饿汉模式,静态内部类里其实就做了饿汉模式的工作。这样,只有当调用getInstance()方法时,才会实例化Singleton。不仅线程安全,也能够保证单例对象的唯一。
总结
虽然单例模式是最容易理解的一种设计模式,但是其实现方式多样,而且各有优缺点,使用时特别注意。一般而言,Double CheckLock就可以满足程序要求。如果高并发情况下,可以引入volatile关键字。当然,静态内部类方式也是很好的一种解决方案。