• 设计模式—单例模式2·实现方式


    前言

        前面(单例模式1·思维过程)介绍了单例模式是什么东东,并且在最后让Student类实现了单例模式。但是,单例模式实现的方式不仅仅只有单例模式1中演示的那一种,其实方式有很多。这里介绍常用的几种单例模式的实现方式:

      1.饿汉式

      2.懒汉式

      3.懒汉式的进阶方式——双重验证

      上面三个名词听不懂不要紧,先有个印象就行,下面听我慢慢扯~~

     

    一、饿汉式

        在单例模式1中介绍的那种实现方式就被称作为饿汉式,当Student类被加载到内存中的时候,我们创建的这个单例(Student类的对象)就已经被创建完成了。下面是这种实现方式的具体代码

    class Student {
        
            private static Student s = new Student();
            private Student() {        
            }
        
            public static Student getInstance() {    
            return s;
    }
     }

        从代码中明显可以看到,由于在声明Studnet的对象s的时候就已经初始化了,那么,自然不管后续有没有使用到这个对象,都会在内存中创建出这个对象。这种还没有使用就先创建个对象在那等着的方式显得比较饥渴,所以叫做饿汉式。

      

    二、懒汉式

        与饿汉式相对的就是懒汉式,聪明的小伙伴应该已经猜到了,既然没有用到就创建好对象等着人家来用叫做饿汉式,那么,等到用的时候再创建自然就是懒汉式了。

        代码如下所示:

    class Student {
        
            private static Student s = null;
        
            private Student() {
            }
            public static Student getInstance() {
    
                if(s==null) {        
                    s = new Student();        
                }
                return s;
            }
    }

        Student刚加载到内存的时候明显不会创建Student类的对象,因为声明是null在需要使用Student对象也就是调用getInstance()方法的时候再进行判断,如果对象s没有被创建,这时候再new一个对象出来。这种用的时候再创建的方式就是懒汉式,显得比较懒~~

     

    三、懒汉式的进阶方式——双重验证

        上面介绍了两种实现单例模式的方式:

        第一种不管人家用不用到这个单例对象(这里就是Student的对象s),都会先创建好放那放着,这明显有点浪费的嫌疑;

        第二种虽然在用的时候才创建,但是在多线程的情况下不能保证单例,比如有两个线程AB同时访问了getInstance()方法。线程A进入if后停止,开始等待,没有创建对象s;此时cpu又开始执行线程B,线程B也进入了if;之后线程A继续往下执行创建了对象s,此时线程B已经进入了if,所以线程B也会创建一个对象s,这样就产生了两个Student的对象,就不是单例了。

    3.1、初级方案

        为了避免上面这种情况的发生,我们可以给getInstance()方法加上一个线程同步锁,保证getInstance()方法同一时间只有一个线程能访问,代码如下所示:

    class Student {
        
            private static Student s = null;
            private Student() {        
            }
    
            public static synchronized Student getInstance() {
        
                if(s==null) {
                    s = new Student();        
                }
                return s;
            }
    }

        这样,在A线程访问(进入)getInstance()方法的时候B线程肯定只能在外面等着,也就是阻塞状态,当A线程办完事之后B线程才能进入,但是此时s已经不是null了,B线程无法进入if,所以不会重复创建对象。

     

    3.2、终极方案

        做了3.1中的处理之后,似乎是解决了问题,但是又产生了一个新的问题,就是程序的效率问题。比如有十个线程同时想要访问getInstance()方法,此时A线程先进去了,那么剩下的九个就只能阻塞在外面等着,啥也不能干,等A干完了B再进去,后面8个等着······这是线程锁的特性,线程只能阻塞在此,不能继续往下执行。

        为了解决这个问题,代码应该改进成下面这种形式:

    class Student {
        
            private static Student s = null;
        
            private Student() {            
            }    
            public static  Student getInstance() {
            
                if(s==null) {        
                    synchronized(Student.class) {        
                        if(s==null) {
                            s = new Student();            
                        }
                    }        
                }
                return s;
            }
    }

        上面的代码是这样解决效率问题的:

        当A线程进入同步锁包裹的内容之后,创建Student对象之前,B线程进入了第一个if里面,但是给同步锁挡在了第二个if外面,其余线程还在第一个if的外面。情况如下图所示:

        

        此时B是阻塞状态,等到A线程搞定自己的事情之后(创建Student类的对象),B线程进入同步锁包裹的内容,但是给第二个if挡在了外面,因为对象s已经被创建,这样就不会再次创建Student的对象,保证了单例。另外,其余八个线程被挡在了第一个if外面,直接跳过了同步锁的内容执行下面的代码了(没有同步锁,单纯的if条件不成立的话直接跳过if包裹的代码,执行下面的代码),也不会产生阻塞,所以,这种写法只会产生一次阻塞或者几次阻塞,不会特别影响程序运行的效率。

     

    四、总结

      1.java中实现单例模式的方式还有很多,但是上面这几种就够用了,搞得太高端并没有什么卵用。

      2.经过上面的一大波分析,似乎懒汉式——双重验证这种方式最碉堡,应该用它,其实不然。占一点内存对我们来说影响不大,但是多写好多代码的话宝宝就不开心了。所以我们最应该使用的还是第一种——饿汉式。那为什么我们分析这么多呢,这就像打LOL一样,经常一顿分析猛如虎,团战打完0-5,现实就是这个样子滴~~

      3.懒汉式的第一种虽然线程不安全,但是不是没有用的,因为很多时候代码中并不要求线程安全。

     

     

     

     

     

     

     

     

  • 相关阅读:
    安卓逆向前置之JAVA学习
    @Controller VS @RestController @RequestBody VS @ResponseBody
    【ArangoDb踩坑】ArangoDb中的大数比较
    我的快排
    记录改造ffmpeg遇到的依赖库问题
    centos7 配置阿里yum源
    记录一个解决GLIBC_2.18 not found的问题
    js 格式化时间 供页面使用
    5G PDU session Establishment
    DPDK performance for USER application
  • 原文地址:https://www.cnblogs.com/Bingfengwangzuo/p/6851452.html
Copyright © 2020-2023  润新知