懒汉式单例模式、饿汉式单例模式 以及 解决如何懒汉式单例模式线程安全问题

单例模式

1. 定义

什么是单例模式呢? 我们引用一下维基百科:

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。还有就是我们经常使用的servlet就是单例多线程的。使用单例能够节省很多内存。

2. 如何使用单例模式

​ 实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

3. 单例模式的实现

3.1 饿汉式

​ 为什么叫饿汉式呢?我们看代码,其中定义了一个 私有的(保证外部不能直接访问) 静态的 final的实例,并且直接new了一个对象,并将构造方法也设置为私有的(保证外部不能创建实例),这样就会导致Singleton 类在加载字节码到虚拟机的时候就会实例化这个实例,当你调用getInstance方法的时候,就会直接返回,不必做任何判断,这样做的好处是代码量明显减少了,坏处是,当我们没有使用该单例的时候,该单例却被加载了,如果这个单例很大的话,将会浪费很多的内存。

static class Singleton
{
   //创建static成员变量,标识Singleton类的唯一实例
    private static final Singleton instance = new Singleton();

    //创建private权限的构造方法
    private Singleton()
    {

    }

    /**
     * 获取实例的唯一方式,确保得到实例唯一
     * 在类外部可以获取到唯一实例
     * @return
     */
    public static Singleton getInstance()
    {
        return instance;
    }
}

3.2 懒汉式

我们可以看到,这是一个简单的获取单例的一个类,首先我们定义一个私有静态实例 single, 并将构造方法变成私有的(保证外部不能创建实例),并且给外界一个静态获取实例的方法。如果对象不是null,就直接返回实例,从而保证实例。也可以保证不浪费内存。但是有部分问题。

static class Singleton
{
    //创建static成员变量,标识Singleton类的唯一实例
    private static Singleton instance = null;

    //创建private权限的构造方法
    private Singleton()
    {

    }

    /**
     * 获取实例的唯一方式,确保得到实例唯一
     * 在类外部可以获取到唯一实例
     * 第一次实例化时 线程可能不安全
     * @return
     */
    public static Singleton getInstance()
    {
        //如果为空,说明实例还未存在(即第一次使用),则创建实例
        if (instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}

4. 线程安全问题

通过对代码的分析,我们可以发现

饿汉式单例模式线程安全的,由于饿汉式单例模式在加载时已经创建了对象,这就保证了在其他线程再次访问时将会访问到同一个对象。

而上述的懒汉单例模式线程不安全,如下图所示,如果在线程1发现instance为空后,线程2抢占到了执行权,也进行了判断,接着两个线程都发现instance为空,将会创建两个不同的instance对象返回,所以这就不能保证单例模式,即不是线程安全的

image-20210804212653692

5. 解决懒汉式线程安全问题

我们可以通过三个方面来优化并解决懒汉式线程安全问题

  • 加锁 synchronized保证同时只有一个进程进入此方法,从而保证并发安全。

    synchronized (Singleton.class)
    {
        if (instance == null)
        {
            instance = new Singleton();
        }
    }
    
  • 双if

    由于加锁只是为了避免第一次创建实例时线程不安全,其后都只会降低性能,所以添加if判断,当发现其为空时才加锁,否则直接返回已经创建好的实例对象。

    if (instance == null) //由于加锁只是为了避免第一次创建实例时线程不安全,其后都只会降低性能,所以添加if判断
    {
        //如果有多个线程进入进入同步块,其他线程等待
        synchronized (Singleton.class)
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
        }
    }
    
  • 添加volatile

    由于会进行 instance == null 判断(多个线程会出现一个读一个写),编译器优化后可能导致内存可见性问题

    //创建static成员变量,标识Singleton类的唯一实例,为避免内存可见性问题,添加volatile
    private static volatile Singleton instance = null;
    

综上,优化后的懒汉式单例模式代码如下

static class Singleton
{
    //创建static成员变量,标识Singleton类的唯一实例,为避免内存可见性问题,添加volatile
    private static volatile Singleton instance = null;

    //创建private权限的构造方法
    private Singleton()
    {

    }

    /**
     * 获取实例的唯一方式,确保得到实例唯一
     * 在类外部可以获取到唯一实例
     * 第一次实例化时 线程可能不安全
     * @return
     */
    public static Singleton getInstance()
    {
        //如果为空,说明实例还未存在(即第一次使用),则创建实例
        //加锁,确保判断为空和 new对象两个操作 成为原子操作
        if (instance == null) //由于加锁只是为了避免第一次创建实例时线程不安全,其后都只会降低性能,所以添加
        {
            synchronized (Singleton.class)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
            }
        }
		//如果不为null,直接返回已经创建好的对象
        return instance;
    }
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值