单例模式
描述:
- 作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
使用场景:
- 单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1. 需要频繁实例化然后销毁的对象。
2. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3. 有状态的工具类对象。
4. 频繁访问数据库或文件的对象。
- 应用场景举例:
1. 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
2. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
3. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
4. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
特点
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
饿汉式单例类
public class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();
/**
* 私有默认构造子
*/
private EagerSingleton(){}
/**
* 静态工厂方法
*/
public static EagerSingleton getInstance(){
return instance;
}
}
上面的例子中,在这个类被加载时,静态变量instance会被初始化,此时类的私有构造子会被调用。这时候,单例类的唯一实例就被创建出来了。
饿汉式其实是一种比较形象的称谓。既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例。
- 饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
懒汉式单例类
public class LazySingleton {
private static LazySingleton instance = null;
/**
* 私有默认构造子
*/
private LazySingleton(){}
/**
* 静态工厂方法
*/
public static synchronized LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
- 上面的懒汉式单例类实现里对静态工厂方法使用了同步化,以处理多线程环境。
懒汉式其实是一种比较形象的称谓。既然懒,那么在创建对象实例的时候就不着急。会一直等到马上要使用对象实例的时候才会创建,懒人嘛,总是推脱不开的时候才会真正去执行工作,因此在装载对象的时候不创建对象实例
懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间
由于懒汉式的实现是线程安全的,这样会降低整个访问的速度,而且每次都要判断。那么有没有更好的方式实现呢?
双重检查加锁
- “双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
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;
}
}
- 这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
- volatile关键字的含义是:被其所修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存来实现,从而确保多个线程能正确的处理该变量。该关键字可能会屏蔽掉虚拟机中的一些代码优化,所以其运行效率可能不是很高,所以,一般情况下,并不建议使用双重加锁机制,酌情使用才是正理!
类级内部类方式
- 饿汉式会占用较多的空间,因为其在类加载时就会完成实例化,而懒汉式又存在执行速率慢的情况,双重加锁机制呢?又有执行效率差的毛病,有没有一种完美的方式可以规避这些毛病呢?
- 貌似有的,就是使用类级内部类结合多线程默认同步锁,同时实现延迟加载和线程安全。
public class ClassInnerClassDanli {
public static class DanliHolder{
private static ClassInnerClassDanli dl = new ClassInnerClassDanli();
}
private ClassInnerClassDanli(){}
public static ClassInnerClassDanli getInstance(){
return DanliHolder.dl;
}
}
如上代码,所谓类级内部类,就是静态内部类,这种内部类与其外部类之间并没有从属关系,加载外部类的时候,并不会同时加载其静态内部类,只有在发生调用的时候才会进行加载,加载的时候就会创建单例实例并返回,有效实现了懒加载(延迟加载),至于同步问题,我们采用和饿汉式同样的静态初始化器的方式,借助JVM来实现线程安全。
其实使用静态初始化器的方式会在类加载时创建类的实例,但是我们将实例的创建显式放置在静态内部类中,它会导致在外部类加载时不进行实例创建,这样就能实现我们的双重目的:延迟加载和线程安全。