单例模式

一、单例模式

定义: 一个类只有一个实例,并且自行实例化向整个系统提供一个全局访问点 (向整个系统提供这个实例)。
类型: 创建型模式
应用场景: 有状态的工具类对象; 频繁访问数据库或文件的对象. 等等
结构:
在这里插入图片描述                   
关键点:
构造函数私有化—禁止在其他类中创建该类的对象
成员变量为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<Singleton> threadlocal = new ThreadLocal<Singleton>();
    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实例.

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值