单例模式(Singleton Pattern):作为对象的创建模式,单例模式确保某个类只有一个对象,而且能自行实例化并向整个系统提供这个实例。
为什么要创建单例模式?
有一些对象我们只需要一个,比方说:线程池(threadPool)、缓存(cache)、对话框、注册表(registry)的对象、日志对象,充当打字机,显卡等设备的驱动程序的对象。事实上,这些对象只能有一个实例,如果创造了过个实例,会导致很多问题,例如:程序的行为异常、资源使用过度,或发生不一致的后果。
单例模式有哪些不好的方面呢?
- 开销: 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
- 可能的开发混淆:使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
单例类的生命周期的讨论?
关于这个问题,有过一些争论。有人认为“jvm垃圾回收机制会把长久不用的单例类对象当作垃圾,并在cpu空闲的时候对其进行回收”,而在实际项目过程中我们会发现“jvm不会回收长久不用的单例对象”。还有人认为 “java单例模式是否会被垃圾回收,主要是取决于垃圾回收的算法,而由于不同的虚拟机使用的垃圾回收算法也不同,所以是否会被回收还是要看是在什么样的虚拟机中运行。”
单例模式的特点:
- 单例类只能有一个实例;
- 单例类必须自己创建这个实例;
- 单例类必须自行向整个系统提供实例。
从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
第一种:饿汉模式:
//饿汉模式
public class Singleton {
//含有一个该类内部的静态私有对象
private final static Singleton instance = new Singleton();
//只提供私有的构造函数(1,覆盖默认的公有的构造函数;2,生成内部的静态实例时会用到)
private Singleton(){
}
//提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
public static Singleton getInstance (){
return instance;
}
}
上面的例子中,在这个类被加载时,静态变量instance会被初始化,此时类的私有构造子会被调用。这时候,单例类的唯一实例就被创建出来了。饿汉式其实是一种比较形象的称谓。既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例。饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
第二种方式:懒汉模式
//懒汉模式
public class SingletonLazy {
//含有一个该类内部的静态私有对象
private static SingletonLazy instance = null;
//只提供私有的构造函数(1,覆盖默认的公有的构造函数;2,生成内部的静态实例时会用到)
private SingletonLazy(){
}
//提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
public static SingletonLazy getInstance(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
}
懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
第三种方式:双重检查加锁(线程安全)
public class Singleton {
//含有一个该类内部的静态私有对象
private static Singleton instance =null ;
//只提供私有的构造函数(1,覆盖共有的构造函数;2,生成内部的静态实例时会用到)
private Singleton(){
}
//提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized(Singleton.class){
//再次检查实例是否存在,如果不存在才真正的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
第四种:Initialization on Demand Holder模式(使用一个持有类,主要是为了不在初始化的时候加载)
要想很简单地实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式。但是这样一来,不是会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。
什么是类级内部类
简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。
public class Singleton {
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
*/
private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static Singleton instace = new Singleton();
}
//只提供私有的构造函数(1,覆盖共有的构造函数;2,生成内部的静态实例时会用到)
private Singleton(){
}
//提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
public static Singleton getInstance(){
return SingletonHolder.instace;
}
}