美文网首页
设计模式|你真的会写单例模式吗?

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

作者: 匍匐前行的90后 | 来源:发表于2023-02-22 15:58 被阅读0次

    前言

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

    单例模式

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

    要点:

    • 确保程序中一个类最多只有一个实例。
    • 提供访问这个实例的全局点。

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

    /**
     * 单例模式
     * @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"));
        }
    }
    

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

    总结

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

    相关文章

      网友评论

          本文标题:设计模式|你真的会写单例模式吗?

          本文链接:https://www.haomeiwen.com/subject/yspakdtx.html