Java单例模式的完整实现,从0到1带你一步步优化

设计模式----单例模式

设计模式是前人在经验之后总结出来的代码逻辑,或者说是框架,用于更好更快的实现业务需求。设计模式有许许多多,这里介绍的单例模式就是其中的一种。

单例模式的意义和简单实现

在很多时候我们创建一个类并不需要很多的实例,只需要一个实例就够了,例如如果我们把太阳作为对象,实现它发光发热的功能,由于太阳只有一个,我们创建一个太阳类后只需要一个对象。这样的需求和static的功能不谋而合,只要我们在类中创建好一个static实例并把构造方法设置为私有,这样在类外就只有一个太阳的实例对象了,代码如下:

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton(){//使用private进行封装,外界无法通过构造方法另外创建实例

    }

    public static Singleton getInstance(){
        return instance;
    }
}

这样我们就实现了最简单的单例模式,在外界想要引用这个唯一的对象,只要调用getInstance()即可,这种实现方法被称为饿汉模式,与饿汉模式对应的还有一个懒汉模式。

懒汉模式

饿汉模式的缺陷是instance这个实例对象在类加载阶段就会创立,也就是说程序开始运行时就会创建这个实例对象,这样的模式少了还好,但是如果不加管控就会导致因为需要大量加载数据程序刚开始运行时速度极其缓慢,所以就有了懒汉模式。“懒”这个词在编程中往往是一个褒义词,懒汉模式是把对象的实例化往后推迟,什么时候要用什么时候创建,具体实现如下:

public class SingletonLazy {
    private static SingletonLazy instance = null;
    private SingletonLazy(){

    }

    public static SingletonLazy getInstance(){
        if(instance == null){
            instance = new SingletonLazy();
        }
        
        return instance;
    }
}

这种实现虽然在单线程中天衣无缝,但是一旦放入多线程中,弊病就显露了出来。

懒汉模式在多线程中的优化

这里的问题主要有两处:

  1. 如果开始时多个线程同时调用getInstance,由于对象没有实例化,导致每个线程都创建了一个实例化对象,使得对象被多次实例化,所以我们可以使用synchronized给getInstance加锁限制多个线程同时实例化:
public static SingletonLazy getInstance(){
    synchronized (SingletonLazy.class){
        if(instance == null){
            instance = new SingletonLazy();
        }
    }

    return instance;
}

但是如果这样做会导致之后所有线程都只能有一个调用getInstance,没有了并发执行的优势,而我们的需求只是限制在instance为null时禁止多个线程同时调用getInstance,所以我们可以在if上层再加上一层判断:

public static SingletonLazy getInstance(){
    if(instance == null){
        synchronized (SingletonLazy.class){
            if(instance == null){
                instance = new SingletonLazy();
            }
        }
    }
    
    return instance;
}

代码上虽然两个if内容相同,但是作用是天差地别,第一个if是让instance不为null时直接返回,第二个if是判断在多个线程同时进入第一个if后是否已经有过实例化,如果没实例化就创建,有就结束。

  1. 即使到这一步,代码仍有一些缺陷,在程序运行时new有三个指令:

在这里插入图片描述

在JVM中为了性能优化,JVM可能会把2 和 3两个指令进行调换,这样的调换虽然在单线程中没有影响,但是在多线程中可能出现返回空引用的情况,即线程a先new一个对象,先执行3指令,此时instance已经不为空,但是2还没执行时另一个线程直接调用了getInstance,这就导致了空引用传递,图示如下:

在这里插入图片描述

为了避免指令重排序,我们可以使用volatile关键字进行修饰,完整代码如下:

private static volatile SingletonLazy instance = null;//使用volatile修饰,禁止getInstance时指令重排序
private SingletonLazy(){

}

public static SingletonLazy getInstance(){
    if(instance == null){
        synchronized (SingletonLazy.class){
            if(instance == null){
                instance = new SingletonLazy();
            }
        }
    }

    return instance;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最后一只三脚兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值