深入灵魂的考验,每行注释都是灵魂的单例模式,源码+实例降临

不管是设计模式也好,别的模式也要,他都是为了解决问题而发明的有效的方法。除了我们已经熟悉的23种设计模式以外,还有MVVMCombinator等其它的东西,都已经是前辈们经过多年的摸爬滚打总结出来的,其有效性不容置疑。我这篇文章也不会用来证明设计模式是有用的,因为在我看来,这就跟1+1=2一样明显(在黑板上写下1+1=2)

而这,在现在这个追求高质量代码的时代,虽然显得有一些复杂,但是我个人还是“推崇”(看好了,我有引号的)这个东西,毕竟面试必问系列,你咋整

来看今天的内容吧,有代码,有实例,并且有一些内容我直接放在代码中通过注释进行讲解,会更好理解

一、设计部分:单例的实现思想、代码及注意问题

packagecom.test.hibernate;/*生成一个懒汉式单例的基础理解:

1.Singleton顾名思义就是只能创建一个实例对象。。所以不能拥有public的构造方法

2.既然构造方法是私有的,那么从外面不可能创建Singleton实例了。。只能从内部创建。。所以需要一个方法来创建此实例,也因此只能通过类名来创建对象。。此方法肯定必须是static的

3.静态的getInstance方法要返回一个Singleton实例。。就要一个Singleton类型的变量来存储。。声明一个Singleton类型的属性。。同样需要是static 的。。静态方法只能访问静态属性。。。

!!!前3步是单例的共性!后三步是懒汉式需要考虑的地方!

4.为了保证只生成一个实例,需要做判断是否为null

5.此时考虑线程问题,假设有两个线程。。thread1,thread2。。thread1运行到判断那个Singleton类型的变量是否为null,然后跳到了thread2。。也运行到判断之后。。。此时两线程都得到single为空。。。那么就会有两个实例了。。。解决办法。。同步

6.同步又要考虑效率,不能有太多的没用同步

* *///思考总结:其实饿汉式没什么问题,问题就是出现在懒汉式上,一般就是牵扯到执行效率和线程安全2个角度上来思考//个人感觉 如果是饿汉式就用天然的没毛病,如果想用懒汉式就用静态内部类方式吧//存在问题:如何在2个jvm上保证单例还未解决:这个就牵扯到分布式锁,可以用zookeeper来实现//还缺少一种懒汉式的枚举方式实现有待研究,听说这个方法也不错。publicclassdanli{//模拟一下静态代码块的使用方式,静态代码块在类加载的运行,先静态代码块》再构造代码块》再构造函数 ,只研究单例可以忽略publicstaticfinalString STR1;static{        STR1 =newString("zzh");    }}//下面正式演示各种单例的实现:classdanli2{//单例饿汉式(非延时加载),提前加载,有利于速度和反应时间,天然的线程安全的。没毛病privatedanli2(){};privatestaticfinaldanli2 two =newdanli2();//final可加可不加,final的目的就是最终的,只允许一次赋值,但不加是因为没法在本类外给他赋值了,因为构造方法是私有的没法创建这个类的对象了,而且这个成员变量也是私有的所以不能在外面调用到,但是可以在本类中的其他方法调用到,所以其实还是可以修改的,所以还是加上final吧publicstaticdanli2getSingleInstance(){returntwo;    }}classdanli3{//单例懒汉式(延时加载),用的时候再去加载,有利于资源充分利用privatedanli3(){};privatestaticdanli3 three =null;publicstaticsynchronizeddanli3getSingleInstance(){//加上synchronized变得线程安全了,但是效率下降了,每次还需要检查同步等等if(three ==null){//保证只生成一个实例three =newdanli3();        }returnthree;    }}/* 该类跟上面那个是一样的,上面是synchronized方法,下面这个是代码块。

class Singleton {

private Singleton() {}

private volatile static Singleton instance = null;

public static Singleton getInstance() {

  synchronized (Singleton.class) {//利用synchronized代码块,每次需要先检查有没有同步锁,效率较低,为了解决这个问题又提出了加入双层检查,也就是在这个同步代码块的外面再加一层为null判断,来减少除第一次以外的同步检查,提高了效率

    if (instance == null) {

    instance = new Singleton();

    }

  }

  return instance;

}

}

*/双重检查加锁就是在同步代码块的外面一层再来一个==null的判断,解决除第一次以外所有的同步判断导致的效率下降问题//但是这个双重检查加锁在多线程环境下存在系统崩溃的可能(一个线程初始化一半的对象,被第二个线程直接拿去用了,所以系统崩溃了)/*原因如下

1、线程 1 进入 getInSingleton() 方法。

2、由于 uniqueInstance 为 null,线程 1 在 //1 处进入 synchronized 块。

3、线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。

4、线程 1 被线程 2 预占。

5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 uniqueInstance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。

6、线程 2 被线程 1 预占。

7、线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。

*/classSingleton{//双重检查加锁,线程相对安全了,避开了过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同获取锁了),效率比上面那个能提高一些// volatile关键字确保当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量,这个关键字其实也解决了上面说的系统可能崩溃的问题,因为使用这个变量也需要一个线程一个线程的来使用了privatevolatilestaticSingleton uniqueInstance;privateSingleton(){    }publicstaticSingletongetInSingleton(){if(uniqueInstance ==null) {// 检查实例,如是不存在就进行同步代码区synchronized(Singleton.class){//1 // 对其进行锁,防止两个线程同时进入同步代码区if(uniqueInstance ==null) {//2 // 双重检查,非常重要,如果两个同时访问的线程,当第一线程访问完同步代码区后,生成一个实例;当第二个已进入getInstance方法等待的线程进入同步代码区时,也会产生一个新的实例uniqueInstance =newSingleton();//3}            }        }returnuniqueInstance;    }// ...Remainder omitted}//使用静态内部类是没问题的,而且效率也不会降低,而且还是懒加载classSingleton2{//jvm加载SingletonHolder的时候会初始化INSTANCE,所以既是lazy的又保证是单例的privatestaticclassSingletonHolder{//静态内部类,只会被加载一次(在加载外部类的时候),所以线程安全,注意静态只能使用静态staticfinalSingleton2 INSTANCE =newSingleton2();    }privateSingleton2(){}//静态构造方法publicstaticSingleton2getInstance(){//对外提供单例的接口returnSingletonHolder.INSTANCE;    }}classceshi{//只是简单测试了一下单例,都为true,可以忽略publicstaticvoidmain(String[] args){        System.out.println(danli.STR1 == danli.STR1);//trueSystem.out.println(danli2.getSingleInstance() == danli2.getSingleInstance());        System.out.println(danli3.getSingleInstance() == danli3.getSingleInstance());        System.out.println(Singleton.getInSingleton() == Singleton.getInSingleton());        System.out.println(Singleton2.getInstance() == Singleton2.getInstance());    }}

二、应用部分:单例的适用场景

优点:

第一、能减少资源的使用,但有时需要通过线程同步来控制资源的并发访问;也避免对共享资源的多重占用

第二、控制实例产生的数量(允许可变数目的实例),由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。

第三、作为通信媒介使用,也就是数据共享,共享这一个对象一个实例(如线程池),它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信,但注意多线程同步问题。

缺点:

1.不太适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态,所以就算保存了,需要加入同步机制来避免错误。

2.由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

3.单例类的职责过重,在一定程度上违背了“单一职责原则”。

4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

使用注意事项:

1.使用时不能用反射模式创建单例,否则会实例化一个新的对象

2.使用懒单例模式时注意线程安全问题

3.饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)

适合场景:

1、有频繁实例化然后销毁的情况,也就是频繁的 new 对象,可以考虑单例模式;

2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象;

3、频繁访问 IO 资源的对象,例如数据库连接池或访问本地文件;

4、单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。

具体应用场景举例:

外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。

内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件,在我们日常使用的在Windows中也有不少单例模式设计的组件,象常用的文件管理器。由于Windows操作系统是一个典型的多进程多线程系统,那么在创建或者删除某个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象。采用单例模式设计的文件管理器就可以完美的解决这个问题,所有的文件操作都必须通过唯一的实例进行,这样就不会产生混乱的现象。

Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~

windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

网站的计数器,一般也是采用单例模式实现,否则难以同步。

应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。

数据库连接池的设计一般采用单例模式,数据库连接是一种数据库资源。软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的。当然,使用数据库连接池还有很多其它的好处,可以屏蔽不同数据数据库之间的差异,实现系统对数据库的低度耦合,也可以被多个系统同时使用,具有高可复用性,还能方便对数据库连接的管理等等。数据库连接池属于重量级资源,一个应用中只需要保留一份即可,既节省了资源又方便管理。所以数据库连接池采用单例模式进行设计会是一个非常好的选择。

多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

spring的bean(scope)默认是single,当然也可以当然也可以设置为prototype,比如struts2的action就必须是prototype,因为请求不同,一个请求对应一个action对象。

我们知道单例会发生线程安全问题,那么spring是怎么来解决的呢?

问题:当Bean对象对应的类存在可变的成员变量并且其中存在改变这个变量的线程时,多线程操作该Bean对象时会出现线程安全。原因:当多线程中存在线程改变了bean对象的可变成员变量时,其他线程无法访问该bean对象的初始状态,从而造成数据错乱解决方式:1.在Bean对象中尽量避免定义可变的成员变量;2.在bean对象中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中

2个具体场景案例

1、网站在线人数统计;

其实就是全局计数器,也就是说所有用户在相同的时刻获取到的在线人数数量都是一致的。要实现这个需求,计数器就要全局唯一,也就正好可以用单例模式来实现。当然这里不包括分布式场景,因为计数是存在内存中的,并且还要保证线程安全。下面代码是一个简单的计数器实现。

publicclassCounter{privatestaticclassCounterHolder{privatestaticfinalCounter counter =newCounter();    }privateCounter(){        System.out.println("init...");    }publicstaticfinalCountergetInstance(){returnCounterHolder.counter;    }privateAtomicLong online =newAtomicLong();publiclonggetOnline(){returnonline.get();    }publiclongadd(){returnonline.incrementAndGet();    }    .......}

1、配置文件访问类;

项目中经常需要一些环境相关的配置文件,比如短信通知相关的、邮件相关的。比如 properties 文件,这里就以读取一个properties 文件配置为例,如果你使用的 Spring ,可以用 @PropertySource 注解实现,默认就是单例模式。如果不用单例的话,每次都要 new 对象,每次都要重新读一遍配置文件,很影响性能,如果用单例模式,则只需要读取一遍就好了。以下是文件访问单例类简单实现:

publicclassSingleProperty{privatestaticProperties prop;privatestaticclassSinglePropertyHolder{privatestaticfinalSingleProperty singleProperty =newSingleProperty();    }/**

    * config.properties 内容是 test.name=kite

    */privateSingleProperty(){        System.out.println("构造函数执行");        prop =newProperties();        InputStream stream = SingleProperty.class.getClassLoader()                .getResourceAsStream("config.properties");try{            prop.load(newInputStreamReader(stream,"utf-8"));        }catch(IOException e) {            e.printStackTrace();        }    }publicstaticSinglePropertygetInstance(){returnSinglePropertyHolder.singleProperty;    }publicStringgetName(){returnprop.get("test.name").toString();    }publicstaticvoidmain(String[] args){        SingleProperty singleProperty = SingleProperty.getInstance();        System.out.println(singleProperty.getName());    }}

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

推荐阅读更多精彩内容