单例模式八大方式详解

单例模式

模式介绍

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

比如 Hibernate 中的 SessionFactory ,它充当数据存储源的代理,并负责创建 Session 对象。 SessionFactory 并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这是就会使用到单例模式。

八种方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举
饿汉式(静态常量)

饿汉式就是不管外界是否需要使用到这个单例,在类本身初始化的时候就已经将实例创建了。

步骤如下
  1. 创建实例属性
  2. 构造器私有化(避免外界访问)
  3. 类的内部创建对象
  4. 向外暴露一个静态的公共方法获取实例
实例演示
public class SingleTon01 {
    // 构造器私有化,禁止外界访问
    private SingleTon01(){}

    // 创建单一实例
    private static final SingleTon01 instance = new SingleTon01();

    // 向外界提供获取唯一实例的方法
    public static SingleTon01 getSingleTon01(){
        return instance;
    }
}
优缺点

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

没有线程同步的问题,是因为不会因为线程调用方法时多次创建实例对象,唯一的实例对象在类初始化时已经创建完毕。

缺点:在类装载的时候就完成实例化,没有达到懒加载(需要才创建,懒惰式)的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

饿汉式(静态代码块)

通过静态代码块的形式创建单例对象。

演示
static {
    instance = new SingleTon01();
}
优缺点

这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

结论:这种单例模式可用,但是可能造成内存浪费

懒汉式(线程不安全)

懒汉即需要才创建,不会主动提前创建。

演示
public class SingleTon02 {
    private static SingleTon02 single02;

    private SingleTon02(){}

    public SingleTon02 getSingle02(){
        if (single02 == null) {
            single02 = new SingleTon02();
        }
        
        return single02;
    }
}
优缺点
  1. 起到了懒加载的效果,但是只能在单线程下使用。

  2. 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

    多线程并发问题,并发进入,导致多个实例创建

结论:在实际开发中,不要使用这种方式。

懒汉式(线程安全,同步方法)

在实例获取方法上添加同步锁。

演示
public class SingleTon03 {
    private static SingleTon03 single03;

    private SingleTon03(){}

    public synchronized SingleTon03 getSingle03(){
        if (single03 == null)
            single03 = new SingleTon03();
        return single03;
    }
}
优缺点
  1. 解决了部分线程不安全问题,并不是完全的线程安全

  2. 效率太低了,每个线程在想获得类的实例时候,执行getSingle03()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。

    锁的范围太大,影响多线程执行效率

结论:在实际开发中,不推荐使用这种方式。

懒汉式(线程不安全,同步代码块)

通过将synchronized关键字的范围缩小,同步部分代码,提高多线程并发执行下的效率。

演示
public class SingleTon04 {
    private static SingleTon04 singleTon04;
    
    private SingleTon04(){}
    
    public static SingleTon04 getSingleTon04(){
        if (singleTon04 == null){
            synchronized (SingleTon04.class){
                singleTon04 = new SingleTon04();
            }
        }
        return singleTon04;
    }
}
优缺点
  1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块

  2. 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

    这个同步代码块锁的是整个类,在没执行这个方法前,已经有多个线程进入到这个类中,那么锁就无法对已经进入的线程起到作用。(可重入锁)

结论:在实际开发中,不能使用这种方式

改进

双端检锁加volatile修饰,单双端检锁也是线程不安全的,涉及到对象内存的分配问题。

public class SingleTon04 {
    // 在变量上添加volatile是增加变量的可见性
    private volatile static SingleTon04 singleTon04;

    private SingleTon04(){}

    // 双端检锁为了防止在执行该方法前已经进入该类的线程
    public static SingleTon04 getSingleTon04(){
        if (singleTon04 == null){
            synchronized (SingleTon04.class){
                if (singleTon04 == null)
                singleTon04 = new SingleTon04();
            }
        }
        return singleTon04;
    }
}
优缺点
  1. Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证部分线程安全了。
  2. 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象,也避免的反复进行方法同步.
  3. 线程安全、延迟加载、效率较高

结论:在实际开发中,推荐使用这种单例设计模式

静态内部类

通过内部类的机制来进行单例的创建。

演示
public class Singleton05 {
    private static class getInstance {
        private static final Singleton05 singleton05 = new Singleton05();
    }

    private Singleton05(){}

    public static Singleton05 getSingleTon05(){
        // 直接调用内部类的属性
        return getInstance.singleton05;
    }
}
优缺点
  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getSingleTon05方法,才会装载getInstance内部类,从而完成Singleton的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

结论:推荐使用

枚举

枚举本身就是一个类,是一种语法糖。本身枚举的构造器是私有的。

演示
enum SingleTon06 {
    // instance就是SingleTon06唯一的实例
    INSTANCE;
    public void method(){}
}
优缺点
  1. 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而
    且还能防止反序列化重新创建新的对象。
  2. 这种方式是Effective Java作者Josh Bloch 提倡的方式

结论:推荐使用

单例模式在JDK 应用的源码分析

java.lang.Runtime就是经典的单例模式(饿汉式),这里可以使用饿汉式是因为,在多线程下这个类是一定会用到的,这样也可以保证线程安全。

下面是部分源码

public class Runtime {
    private static Runtime currentRuntime = new Runtime();


    public static Runtime getRuntime() {
        return currentRuntime;
    }
}

单例模式注意事项和细节说明

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new来创建实例对象
单例模式使用的场景

需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值