设计模式(二)单例模式

单例模式

概述

  • 定义 : 确保一个类只有一个实例,并为整个系统提供一个全局访问点。
  • 类型: 创建模式
  • 结构:
                          
    在这里插入图片描述

1、单线程下的单例模式实现

立即加载 :** 在类加载初始化的时候就主动创建实例;

延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。

在单线程环境下的两种经典的实现: 饿汉式单例(立即加载)懒汉式单例(延迟加载)

饿汉式单例

// 饿汉式单例
public class Singleton1 {
 
    // 指向自己实例的私有静态引用,主动创建
    private static Singleton1 singleton1 = new Singleton1();
 
    // 私有的构造方法
    private Singleton1(){}
 
    // 以自己实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton1 getSingleton1(){
        return singleton1;
    }
}

类加载的方式是按需加载,且加载一次。。这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,在JVM层面上执行一次,所以饿汉式 在单/多线程环境下都是天生线程安全。

测试:开10个线程打印hashCode值, 结果都是一样的。

/**
 *
 *@author Saiuna
 *@date 2020/7/6 12:45
 */
public class test {
    public static void main(String[] args) {

        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] =new Thread(()->{
                System.out.println(Singleton1.getInstance().hashCode());
            });;
        }

        for (int i = 0; i <  threads.length; i++) {
            threads[i].start();
        }
    }
}

懒汉式单例

/**
 *传统的懒汉模式,线程不安全
 *@author Saiuna
 *@date 2020/7/6 12:54
 */
public class Singleton2 {

  private static Singleton2 singleton;
  private Singleton2() { }

  public static Singleton2 getInstance() {
    // 被动创建,在真正需要使用时才去创建
    if (singleton == null) {
      singleton = new Singleton2();
    }
    return singleton;
  }
}

实例被延迟加载,用到时才会实例化一个对象并交给自己的引用。

注:懒汉式单例本身非线程安全的,多个线程同时访问会产生多个实例 (用上述测试案例测试会得到不同的hashCode)。至于详细的为什么不安全,就涉及到多线程的问题了。

多个线程同时竞争下, singleton的值对于其他线程是不可见的,会发生多个线程同时判断singleton都为null值,从而产生了多个实例。

2、多线程下的单例模式实现

懒汉式单例本身是非线程安全的,在多线程环境中很可能会产生多个实例。那如何保证线程安全呢

2.1 内部类-延迟加载

/**
 *懒汉模式,使用内部类实现延迟加载
 *@author Saiuna
 *@date 2020/7/6 14:11
 */
public class Singleton5 {

    private static class Holder {
        private static Singleton5 singleton5 = new Singleton5();
    }

    private Singleton5() {
    }

    public static Singleton5 getInstance() {
        return Holder.singleton5;
    }

}

使用内部类实现线程安全的懒汉式单例,也是一种效率比较高的做法, 为什么安全呢~与饿汉单例同理。

2.2 双重检查(Double-Check idiom)

/**
 *线程安全的懒汉式单例---双重检查(Double-Check idiom)
 *@author Saiuna
 *@date 2020/7/6 14:21
 */
public class Singleton6 {

    /**
     * 使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
     */
    private static volatile Singleton6 singleton6;

    private Singleton6() {
    }

    public static Singleton6 getInstance() {
        // Double-Check idiom 第一次非 null检查,避免频繁上锁
        if (singleton6 == null) {
            synchronized (Singleton6.class) {
                // 只需在第一次创建实例时才同步
                if (singleton6 == null) {
                    singleton6 = new Singleton6();
                }
            }
        }
        return singleton6;
    }
}

这里必须用volatile 来修饰 实例,恰好写过一篇关于volatile 的文章,对此理解起来更加容易

volatile : 禁止指令重排序(实现: 内存屏障)

那么,如果不用 volatile 修饰 singleton6,会发生什么情形呢?

(1)当我们写了 new 操作,JVM 到底会发生什么?

首先,我们要明白的是: **new Singleton6() 是一个非原子操作。**代码行singleton6= new Singleton6(); 的执行过程可以形象地用如下3行伪代码来表示:

  1. memory = allocate(); //1:分配对象的内存空间
  2. ctorInstance(memory); //2:初始化对象
  3. singleton3 = memory; //3: 使singleton6指向刚分配的内存地址

**但实际上,这个过程可能发生无序写入(指令重排序),也就是说上面的3行指令可能会被重排序导致先执行第3行后执行第2行,**也就是说其真实执行顺序可能是下面这种:

  1. memory = allocate(); //1:分配对象的内存空间
  2. singleton3 = memory; //3:使singleton6指向刚分配的内存地址
  3. ctorInstance(memory); //2:初始化对象

如果2、3发生了重排序就会导致第二个判断会出错,singleton != null,但是它其实仅仅只是一个地址而已,此时对象还没有被初始化,此时别的线程将得到的是一个不完整(未初始化)的对象,后果可想而知。

单例模式的优点

  • 在内存中只有一个对象,节省内存空间;
  • 避免频繁的创建销毁对象,可以提高性能;
  • 避免对共享资源的多重占用,简化访问;
  • 为整个系统提供一个全局访问点。

单例模式的使用场景

单例模式是比较常用的一种设计模式,其核心在于为整个系统提供一个唯一的实例,其应用场景如:

  • 有状态的工具类对象;
  • 频繁访问数据库或文件的对象;
  • 国际化(翻译缓存,频繁用于翻译)

饿汉式 懒汉式的比较

从速度和反应时间角度来讲,饿汉式要好一些;从资源利用效率上说,懒汉式要好一些。

饿汉式

  • ​ 天生线程安全,使用简单。
  • ​ 速度和反应时间角度上好于懒汉。

懒汉式:

  • ​ 资源利用效率好。
  • ​ 但要注意线程安全问题

总结:

  1. 一般建议用饿汉式,天生线程安全,(再者思考一个问题:不用的话写来干嘛!),除非真的资源紧张,注重资源的场景的话还是需要用到懒汉式的。

  2. 在使用单例模式时,我们必须使用单例类提供的公有工厂方法得到单例对象,而不应该使用反射来创建,否则将会实例化一个新对象

  3. 这边只是举例了比较常用的案例,用枚举和ThreadLoad也可以实现, 其中《Effective Java》中就是推荐用枚举~只是有个印象(●’◡’●)

参考《Java并发编程的艺术》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值