双重检查加锁实现
可以使用“双重检查加锁”的方式来实现单例模式,就可以既实现线程安全,又能够使性能不受到大的影响。
所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检
查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
注意:在Java1.4及以前版本中,很多JVM对于volatile关键字的实现有问题,会导致双重检查加锁的失败,因此双重检查加锁的机制只能用在Java5及以上的版本。
看看代码可能会更清楚些,示例代码如下:
public class Singleton {
/**
* 对保存实例的变量添加volatile的修饰
*/
private volatile static Singleton instance = null; private Singleton() {
} public static Singleton getInstance() {
// 先检查实例是否存在,如果不存在才进入下面的同步块
if (instance == null) {
// 同步块,线程安全的创建实例
synchronized (Singleton.class) {
// 再次检查实例是否存在,如果不存在才真的创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
单例模式的懒汉式实现方式体现了延迟加载的思想,即一开始不要加载资源或者数据,一直等,等到马上就要使用这个资源或者数据了,躲不过去了才加载,所以也称Lazy Load,不是懒惰啊,是“延迟加载”,这在实际开发中是一种很常见的思想,尽可能的节约资源。
需要先得到类实例,然后才可以调用,可是这个方法就是为了得到类实例,这样一来不就形成一个死循环了吗
public static Singleton getInstance() {}
更好的方案要想很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程安全性。比如前面的“饿汉式”实现方式,但是这样一来,不是会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就
初始化对象,不管你需不需要。
如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例,这样一来,只要不使用到这个类级内部类,那就不会创建对象实例。从而同时实现延迟加载和线程安全。
看看代码示例可能会更清晰,示例代码如下:
public class SingletonT {
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
*/
private static class SingletonHolder {
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static SingletonT instance = new SingletonT();
} /**
* 私有化构造方法
*/
private SingletonT() {
} public static SingletonT getInstance() {
return SingletonHolder.instance;
}
}
仔细想想,是不是很巧妙呢!
当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建
Singleton的实例,由于是静态的域,因此只会被虚拟机在装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。