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

Posted 曌影

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式—单例模式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.懒汉式的第一种虽然线程不安全,但是不是没有用的,因为很多时候代码中并不要求线程安全。

 

 

 

 

 

 

 

 

 

以上是关于设计模式—单例模式2·实现方式的主要内容,如果未能解决你的问题,请参考以下文章

单例模式(饿汉方式懒汉方式)

单例模式(饿汉方式懒汉方式)

单例模式(饿汉方式懒汉方式)

单例模式(饿汉方式懒汉方式)

设计模式创建型模式之单例设计模式

实现单例模式的8种方式