一、单例模式
定义: 一个类只有一个实例,并且自行实例化向整个系统提供一个全局访问点 (向整个系统提供这个实例)。
类型: 创建型模式
应用场景: 有状态的工具类对象; 频繁访问数据库或文件的对象. 等等
结构:
关键点:
构造函数私有化—禁止在其他类中创建该类的对象
成员变量为static—保证只能在自身类中获取自身对象
公开访问点getInstance—保证其他类可以访问该对象
二、单线程环境下的两种经典实现
1、饿汉式单例:立即加载,在类加载初始化的时候就主动创建实例
public class EagerSingleton {
// 在类加载时就完成了初始化。类加载较慢,但获取对象的速度快
private static EagerSingleton eagerSingleton = new EagerSingleton();//静态私有成员,已初始化
// 私有的构造方法
private EagerSingleton(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法。静态,不用同步,因为在类加载时已经初始化,不会有多线程问题。
public static EagerSingleton getEagerSingleton(){
return eagerSingleton;
}
}
2、懒汉式单例:延迟加载,等到真正使用时才创建实例
public class LazySingleton {
// 比较懒,类加载时不创建实例,因此,累的加载速度快,但运行时获取对象的速度慢
private static LazySingleton lazySingleton;
// 私有的构造方法
private LazySingleton(){}
//静态,同步,公开访问点
public static LazySingleton getLazySingleton(){
// 被动创建,在真正需要使用时才去创建
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
三. 多线程环境下实现单例
1、饿汉式单例在类加载时就自动创建实例,且类在整个生命周期中只加载一次,即单例只会创建一个实例,保证了其在多线程环境下的线程安全。
2、懒汉式单例则不然。因为,存在有多个线程同时进入 if (lazySingleton == null) {…} 语句块的情形发生。
改进方案一:synchronized 方法
public class LazySingleton1 {
private static LazySingleton1 lazySingleton1;
private LazySingleton1(){}
// 使用 synchronized 修饰,临界资源的同步互斥访问,保证了单例
public static synchronized LazySingleton1 getLazySingleton1(){
if (lazySingleton1 == null) {
lazySingleton1 = new LazySingleton1();
}
return lazySingleton1;
}
}
改进方案二:使用 synchronized 块,实现临界资源的同步互斥访问
public class LazySingleton2 {
private static LazySingleton2 lazySingleton2;
private LazySingleton2(){}
public static LazySingleton2 getLazySingleton2(){
synchronized(LazySingleton2.class){ // 使用 synchronized 块,临界资源的同步互斥访问
if ( lazySingleton2== null) {
lazySingleton2 = new LazySingleton2();
}
}
return lazySingleton2 ;
}
}
改进方案三:双重检测同步延迟加载(最优)
public class LazySingleton3 {
//必须使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
private static volatile LazySingleton3 lazySingleton3;
private LazySingleton3() {}
public static LazySingleton3 getLazySingleton3() {
// Double-Check idiom
if (lazySingleton3 == null) {
synchronized (LazySingleton3.class) {
// 在第一次创建实例时才同步
if (lazySingleton3 == null) {
lazySingleton3 = new LazySingleton3();
}
}
}
return lazySingleton3;
}
}
3、 使用内部类实现多线程环境下的单例。
程序运行过程中,JVM调用Holder类的类构造器对其进行初始化,因为JVM能保证一个类的类构造器在多线程环境中被正确的加锁、同步,所以一次只有一个线程去执行这个类的构造器。同时, 在同一个类加载器下,一个类型只会被初始化一次,保证了单例。
public class Singleton {
// 私有内部类,延迟加载
private static class Holder {//singleton是Hold的类成员变量,
private static Singleton singleton = new Singleton();
}
private Singleton() {}
public static Singleton getSingleton() {
return Holder.singleton;
}
}
4、使用ThreadLocal实现多线程环境下的单例。
public class Singleton {
// ThreadLocal 线程局部变量,将单例instance线程私有化
private static ThreadLocal threadlocal = new ThreadLocal();
private static Singleton singleton;
private Singleton() { }
public static Singleton getSingleton() {
// 第一次检查:线程第一次访问,进入if语句块;否则,若线程已经访问过,则直接返回ThreadLocal中的值
if (threadlocal.get() == null) {
synchronized (Singleton.class) {
if (singleton == null) { // 第二次检查:该单例是否被创建
singleton = new Singleton();
}
}
threadlocal.set(singleton); // 将单例放入ThreadLocal中
}
return threadlocal.get();
}
四、使用场景
1、有状态的工具类对象
2、频繁访问数据库或文件的对象
3、需频繁实例化后销毁的对象
4、创建对象消耗多,但又经常用
单例模式的经典使用场景:
1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置(spring中的Bean)。
2.控制资源的情况下,方便资源之间的互相通信。如多线程的线程池设计,因为池要对池中线程进行控制。
应用场景举例:
1.外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件
2. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
10. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.