一、简介:
我们经常有这样的需求: 某一些类应该只存在一个实例 的时候,我们就可以用单例模式来应对.
单例模式:确保一个类只有一个实例,并提供一个全局访问点.
单例模式是所有设计模式中最简单的一个,也是大部分人最早知道的一个设计模式.
二、我们经常用的2种单例模式(懒汉式、饿汉式)
(1)饿汉式:
饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton {
private Singleton() {}
private static Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton ;
}
}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的。
(2)懒汉式
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (null == singleton) {
singleton= new Singleton();
}
return singleton;
}
}
备注
单例模式的懒汉式实现方式体现了延迟加载的思想,什么是延迟加
载呢? 通俗点说,就是一开始不要加载
资源或者数据,一直等,等到马上就要使用这个资源或者数据了,躲不过去了才加载,所以也称
Lazy Load,不是懒惰啊,是“延迟加载”,这在实际开发中是一种很常见的思想,尽可能的节约资源。
单例模式的懒汉式实现还体现了缓存的思想,缓存也是实际开发中非常常见的功能。简单讲就是,如果
某些资源或者数据会被频繁的使用,而这些资源或数据存储在系统外部,比如数据库、硬盘文件等,
那么每次操作这些数据的时候都从数据库或者硬盘上去获取,速度会很慢,会造成性能问题。 一个简单的
解决方法就是:把这些数据缓存到内存里面,每次操作的时候,先到内存里面找,看有没有这些数据,
如果有,那么就直接使用,如果没有那么就获取它,并设置到缓存中,下一次访问的时候就可以直接从内存
中获取了。从而节省大量的时间,当然,缓存是一种典型的空间换时间的方案。
两种单例模式的比较
比较上面两种写法:
1、懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,费判断的时间,当然,如果一直没有人使用的话,那就不会创建实例,节约内存空间。
2、 饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
(3)双重检查加锁
public class Singleton {
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;
}
双重检查详解
可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受到大的影响。
那么什么是“双重检查加锁”机制呢? 所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。 双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。 这种实现方式既可使实现线程安全的创建实例,又不会对性能造成太大的影响,它只是在第一次创建实例的时候同步,以后就不需要同步了,从而加快运行速度。 注意:在Java1.4及以前版本中,很多JVM对于volatile关键字的实现有问题,会导致双重检查加锁的失败,因此双重检查加锁的机制只能用在Java5及以上的版本。
(4)、静态内部类单例模式
public class Singleton {
private static class Holder{
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return Holder.INSTANCE;
}
}
当第一次加载Singleton类时并不会初始化INSTANCE,只有在第一次调用getInstance方法时才会导致INSTANCE被初始化。这种方式不仅能够保证线程安全,也能保证单例对象的唯一性,同时也延长了单例的实例化。
小结
1、双重检查非常适用于高并发,我们熟知的开源库Eventbus,ImageLoader等都是用的双重检查锁方式实现单例
2、单例模式是运用频率很高的模式,但是,由于在客户端通常没有高并发的情况,因此,选择哪种实现方式都不会有太大的影响。即使如此,出于效率考虑,推荐使用DCL单例(双重检查锁定)和静态内部类单例模式。