单例模式在Java语言中,通常有两种构建方式:
- 饿汉方式:指全局的单例实例在类装载时构建
- 懒汉方式:指全局的单例实例在第一次被使用时构建
不管是那种创建方式,它们通常都存在下面几点相似处:
- 单例类必须要有一个 private 访问级别的构造函数。只有这样,才能确保单例不会在系统中的其他代码内被实例化
- instance 成员变量和 uniqueInstance 方法必须是 static 的
饿汉方式(线程安全)
public class Singleton {
//在静态初始化器中创建单例实例,这段代码保证了线程安全
private static Singleton uniqueInstance = new Singleton();
// Singleton类只有一个构造方法并且是被private修饰的,所以用户无法通过new方法创建该对象实例
private Singleton() {
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
所谓 “饿汉方式” 就是说JVM在加载这个类时就马上创建此唯一的单例实例,不管你用不用,先创建了再说,如果一直没有被使用,便浪费了空间,典型的空间换时间,每次调用的时候,就不需要再判断,节省了运行时间。
懒汉式(非线程安全和synchronized关键字线程安全版本 )
public class Singleton {
private static Singleton uniqueInstance;
private Singleton (){
}
//没有加入synchronized关键字的版本是线程不安全的
public static Singleton getInstance() {
//判断当前单例是否已经存在,若存在则返回,不存在则再建立单例
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
所谓 “ 懒汉式” 就是说单例实例在第一次被使用时构建,而不是在JVM在加载这个类时就马上创建此唯一的单例实例。
但是上面这种方式很明显是线程不安全的,如果多个线程同时访问getInstance()方法时就会出现问题。如果想要保证线程安全,一种比较常见的方式就是在getInstance() 方法前加上synchronized关键字,如下:
public static synchronized Singleton getInstance() {
if (instance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
我们知道synchronized关键字偏重量级锁。虽然在JavaSE1.6之后synchronized关键字进行了主要包括:为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升。
但是在程序中每次使用getInstance() 都要经过synchronized加锁这一层,这难免会增加getInstance()的方法的时间消费,而且还可能会发生阻塞。我们下面介绍到的 双重检查加锁版本 就是为了解决这个问题而存在的。
懒汉式(双重检查加锁版本)
利用双重检查加锁(double-checked locking),首先检查是否实例已经创建,如果尚未创建,“才”进行同步。这样以来,只有一次同步,这正是我们想要的效果。
public class Singleton {
//volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() { //检查实例,如果不存在,就进入同步代码块
if (uniqueInstance == null) {
//只有第一次才执行这里的代码
synchronized (Singleton.class) {
//进入同步代码块后,再检查一次,如果仍是null,才创建实例
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
很明显,这种方式相比于使用synchronized关键字的方法,可以大大减少getInstance() 的时间消费。
变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。