设计模式|你真的会写单例模式吗?

2023-02-22  本文已影响0人  匍匐前行的90后

前言

我们都知道,单例模式是设计模式里最简单的模式,无论是代码还是模式的理解都是最简单的,但是那么简单的东西,你真的写对了吗?

单例模式

单例模式——确保一个类只有一个实例,并提供全局访问点。

要点:

乍一看,确实简单,也很好理解,看看怎么实现的,代码:

/**
 * 单例模式
 * @author herongqin
 */
public class RedisSingleton {

    private static RedisSingleton redisSingleton;


    private RedisSingleton(){

    }

    public static RedisSingleton getInstance(){
        if (redisSingleton == null){
            redisSingleton = new RedisSingleton();
        }
        return redisSingleton;
    }
}

说明:目的为了演示,大家不需要关心Redis的内容。

简单吧,把构造器 private 不让别人进行实例化,然后提供一个对外实例化的静态方法,如果想使用这个实例,那就必须通过 getInstance() 方法进行获取具体的实例,是不是满足了单例模式的2个要点?确实是,但是,有句“古话”说得好:程序员要把任何一个应用都当成多线程应用。

提问:如果有多个线程同时去访问getInstance() ,拿到的能确保是同一个实例吗?

 public static RedisSingleton getInstance(){
    if (redisSingleton == null){
        redisSingleton = new RedisSingleton();
    }
    return redisSingleton;
}

那咋搞?这不是违背了单例的核心原则(最多只有一个实例)了吗?有经验的同学,可能已经意识到了,给这段代码加锁啊[赞]。

给 getInstance 加同步锁

我们在 getInstance() 方法上加 synchronized 关键字实现同步锁,这个时候,每个人进入这个方法前,都需要等待上一个线程结束之后,才能进入这个方法,这个时候就可以保证最多只有一个实例了。代码:

 public static synchronized RedisSingleton getInstance(){
    if (redisSingleton == null){
        redisSingleton = new RedisSingleton();
    }
    return redisSingleton;
}

打完收工~!

后来,项目越来越牛B了,自己写的这个单例越来越多的地方在使用,然后他们就发现自己写的代码怎么越来越慢,经过排查,就是因为自己写的这个单例引起的,因为不管你多少人,你都必须先等待上一个人拿完了他才能继续拿,已经验证影响别人的使用了,咋搞?当然是优化了(谁叫甲方是Babababa,哈哈,开玩笑)。

使用 volatile 做双重检查

在静态变量上用volatile 关键字进行修饰,保证变量可见性(多线程下),禁止jvm对该变量进行指令重排,保证了有序性。

/**
 * 单例模式
 * @author herongqin
 */
public class RedisSingleton {
    private static volatile RedisSingleton redisSingleton;
    private RedisSingleton(){
    }
    public static synchronized RedisSingleton getInstance(){
        // 第1次检测
        if (redisSingleton == null){
            synchronized (RedisSingleton.class){
                // 第2次检查
                if (redisSingleton == null){
                    redisSingleton = new RedisSingleton();
                }
            }
        }
        return redisSingleton;
    }
}

在上面的代码中,synchronized 只会锁其中一个片段,而且因为volatile 只会执行一次,所以确保了最多一个实例的特性。

嗯~~ 是不是有点复杂,有没有简单点的,有的!有个更简单的,因为它天生的线程安全,以及默认的private 的构造器,那就是使用枚举实现单例模式。

使用枚举实现单例模式(推荐)

/**
 * 单例模式
 * @author herongqin
 */
public enum RedisSingleton {

    INSTANCE;

    public void set(String key, Object value){
        // 其他代码
    }
    
    public String getString(String key){
        // 其他代码
        return "";
    }

    public static void main(String[] args) {
        // test
        System.out.println(RedisSingleton.INSTANCE.getString("key"));
    }
}

简单吧,但是得从我们正常使用枚举的思维跳出来。

总结

程序员应该把任何一个程序都当成是多线程。共勉~

上一篇下一篇

猜你喜欢

热点阅读