设计模式杂谈(1)单例模式。【使用频率:99.999%,学习难度:0.001%】

作者:solo陈,转载请注明出处。
个人主页:http://www.jianshu.com/users/5c2177416a84/latest_articles
  设计模式是你在学习java道路上必须要学会掌握的,当然也并不是24种设计模式你都要掌握得很透彻。下面列出几项:《设计模式的好处》
一、有助于设计系统架构、增强系统健壮性、利于维护
二、有利于加强思考问题的思维、思考能力
三、有利于自己的编写代码、阅读代码的能力
  当然你在编码设计过程中如果只是为了显摆一下你会某种设计模式这个是没必要的,因为过多的使用并不会给程序带来良好的阅读性,反而会增加系统的复杂度,所以应该因地制宜。其实如何在正确的地方正确的使用设计模式是初学者普遍会问的问题。比如LZ当时就会有这个的问题:学了单例模式,自己也会写,但是在什么情况下使用呢?这个就不得而知了,导致自己很多时候都很迷茫自己究竟有没有学习设计模式的必要。所以LZ写这个文章的初衷是将工作中使用到的结合起来进行说明:
  下面先来说说单例模式。单例模式不论在自己编码或者源代码中都会遇见,所以此模式是你学习时候必须掌握的。该在什么时候使用呢?LZ在学习中也常常问自己,单例模式都有一个共性就是
1、这个类没有状态
  有状态的类:类里面有成员变量,而且成员变量是可变的、比如struts2的action、要求是多例的、因为他是有状态的
  无状态类:类里面没有成员变量,或者有成员变量但是不可变的、或者成员变量是单例的、比如struts1的action、可以是单例的。因为他是没有状态的
2、比如:工作上类似你一个AppServer类作为启动资源类并进行各种初始化工作,那么你的这个类就可以写出单例,因为这个类在使用的过程中并不需要每次都进行new来实例对象,我们只需要单独的一份就可以了。
说白了单例就是你new无数个其实都是一样的,并且在逻辑上如果new多个也会发生逻辑错误。

在网上该模式又分为懒汉、饿汉等几种形式。LZ就不进行细分了,因为LZ并不想在学习单例模式的时候再对文字进行理解记忆。下面就是几种逐渐进化的单例模式:
一、这种也是学习设计模式时老师讲解的最初级版本

//这是在不考虑并发访问的情况下标准的单例模式的构造方式,这种方式通过几个地方来限制了我们取到的实例是唯一的。
public class Singleton {    
  private Singleton(){}    
  private static Singleton singleton ;    
  public static Singleton getInstance (){          
        if(singleton == null){                
              singleton = new Singleton();           
        }          
        return singleton ;      
  }
}

这种写法是在初学是不考虑并发情况的构造方式,通过几点来确定获取唯一的实例:
1、使用private权限的构造器,使得客户端(使用者)不能够随意创建对象
2、使用static关键字来使得属性所指向的对象在每一个类中都是唯一的
3、static方法,使得客户端可以直接通过类.方法名调用。如果没有static就会使得客户端无法获取实例进行调用
  上面的方法属于大学毕业阶段的代码,因为他并没有考虑到如果在多线程环境会造成的影响,如果多个线程(A\B)同时来访问getInstance这个方法,那么在if判断这里A线程判断为空,然而B线程正好在执行singleton = new Singleton(); 创建实例方法,但是并没有真正实例出对象,那么A线程也会继续执行singleton = new Singleton(); 创建实例方法。从而导致会创建多个实例。

二、有人说第一种是没考虑多线程那么就加锁

/*此种加锁方式会导致运行速度降低,当一个线程进行访问的时候,其余所有线程都将挂起等待
*/
public class StupidSynchronizedSingleton {
    private StupidSynchronizedSingleton (){}
    private static StupidSynchronizedSingleton badsingleton ; 

    public synchronized static StupidSynchronizedSingleton getInstance (){
        if(badsingleton == null){
            badsingleton = new StupidSynchronizedSingleton();
        }
        return badsingleton ;
    }
}

由于该方法的做法实在是太愚蠢了,所以LZ给它取名Stupid。上面的做法是将getInstance ()进行同步来解决多线程问题,但是当访问getInstance ()时,其余的线程会进行挂起,造成无谓的等待,显然这种等待是没有必要的。

三、在第二种方法上适当的修改就可以解决那种无谓的等待了。

//这种实现方式实现了双重锁机制
/**
 *  首先要明白在JVM创建新的对象时,主要要经过三步。
              1.分配内存
              2.初始化构造器
              3.将对象指向分配的内存的地址
              但是在new实例的时候有可能是先将对象分配给内存,在初始化。这个时候返回的synsingleton就会出现未知的错误
 *
 */

public class SynchronizedSingleton {
    private SynchronizedSingleton (){}
    private static SynchronizedSingleton synsingleton ; 
    public static SynchronizedSingleton getInstance (){
        if(synsingleton == null){//1
            synchronized (SynchronizedSingleton.class) {
                if(synsingleton == null){//2
                    synsingleton = new SynchronizedSingleton();
                }
            }
        }
        return synsingleton ;
    }
}

上面方法相比较第二种方法做的同步就要正确得多了,并没有在方法上直接进行同步,而是判断了变量是否为null之后再进行同步,否则就直接进行返回,从而减少了在有实例情况下的等待时间。
假设synsingleton为null(1),此时有A/B线程同时执行到synchronized块,假设A线程抢占到资源再次执行synsingleton为null(2)当判断为true时,就进行实例化对象,否则返回。B之后进入同步块是同样。再次执行判断的原因是确保多个线程进入注释1后代码的逻辑正确性。从上面代码中可以看到有两次判断是否为null,这就是所谓的双重锁机制。
上面代码从表面上看是没有任何问题的,但是如果你了解JVM创建对象的逻辑步骤你就会发现上面做法也会出现问题。具体造成问题的原因看代码上面的注释。所以为了避免我们在创建对象时发生的此种问题,我们最好是交给JVM进行。

四、将创建对象的时机将给JVM加载类的时候进行(类加载这里就不详细说明了,有时间单独写一个我对类加载过程的理解的一篇文章)

/*属性为static的会在类加载的时候初始化,所以在初始化进行一半的时候,别的线程      *是无法使用的,这个是jvm保证的
*/
public class InnerSingleton {
    private InnerSingleton (){};
    public static InnerSingleton getInstance (){
        return SingletonInstance.singleton;
    }
    private static class SingletonInstance {
        private static final InnerSingleton singleton = new InnerSingleton();
    }
}

首先static的成员变量会在类加载的时候进行初始化,所以singleton在代码调用之前就已经实例化好了。

五、枚举量来实现单例模式:
1、 自由序列化;
2、 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
3、 线程安全;

public class EnumSingleton {
    private EnumSingleton() {}
    
    private enum InstanceHolder {
        INSTANCE;
        private EnumSingleton value;

        private InstanceHolder() {
            value = new EnumSingleton();
        }
    }
    
    public static EnumSingleton getInstance() {
        return InstanceHolder.INSTANCE.value;
    }
}

枚举形式不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,但是枚举是1.5版本之后的新特性,所以这种方式很少用到

以上就是java单例模式的各种写法,当然在实际运用中你可以使用四、五方法来创建,如果是应聘,面试官叫你写单例模式你可以都写出来然后讲出各自的优缺点,这样相信你对单例模式的掌握已经熟练了。
上面就是LZ对单例模式的理解,感谢各位的收看。这篇文章也是LZ在简书上写的第一篇,后续也会继续分享自己对Java知识的理解,当然LZ并不是什么大牛,也是在不断的学习过程中分享自己理解,有什么问题可以在文章下发留言进行交流。有错的地方LZ也会改正。谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容