定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。
懒汉式:
/**
* 懒汉式单例实现示例
*/
public class Singleton {
private static Singleton uniqueInstance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
饿汉式:
/**
* 饿汉式单例实现的实现
*/
public class Singleton {
private Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
饿汉式、懒汉式其实是一种比较形象的称谓,所谓饿汉式,既然饿,那么在创建对象实例的时候就比较着急,于是就在装载类的时候就创建对象实例。
所谓懒汉式,既然是懒,那么在创建对象实例的时候就不着急,会一直等到马上要使用对象实例的时候才会创建,懒人嘛,总是推托不开的时候才去真正执行工作,因此在装载对象的时候不创建对象实例。
懒汉式单例应用了延迟加载的思想——一开始不要加载资源或者数据,一直等,等到马上就要使用这个资源或者数据了,躲不过去了才加载。
不加同步的懒汉式是线程不安全的,而饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候不会发生并发的。
加了同步的懒汉式模式,即用synchronized修饰静态方法,是线程安全的,但是这样一来,会降低整个访问的速度,而且每次都要判断。
可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受到很大的影响。
所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确地处理该变量。
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instantce == null) { // 第一次检查
synchronized(Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
在某些情况下,JVM已经隐含地执行了同步,这些情况下就不用自己再来进行同步控制了,这些情况包括:
(1) 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时;
(2) 访问final字段时;
(3) 在创建线程之前创建对象时;
(4) 线程可以看见它将要处理的对象时。
既然这样,要想很简单地实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。
public class Singleton {
/**
* 类级的内部类,也就是静态成员的成员式内部类,该内部类的实例与外部类的实现
* 没有绑定关系,而且只有被调用时才会装载,从而实现了延迟加载
*/
public static class SingletonHolder {
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static Singleton instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHandler.instance;
}
}
当getInstance方法第一次被调用的时候,它第一次读取SingletonHandler.instance,导致SingletonHandler类得到初始化:而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
然而,单元素的枚举类型已经成为实现Singleton的最佳方法。
Java的枚举类型实质上是功能齐全的类,因此可以有自己的属性和方法。Java枚举类型的基本思想是通过公有的静态final域为每个枚举常量导出实现的类。从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举。
/**
* 使用枚举来实现单例模式的示例
*/
public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例
*/
uniqueInstance;
public void singletonOperation() {
// 示例方法
}
}