前言接下来,我们将进入设计模式这一章。作为一名程序员,你一定在工作或面试中多次遇到过设计模式。记得2018年大三的时候出去找实习,也问过我知道什么设计模式,但是我个人回答最详细的大概是单例模式,因为我觉得这个应该是最好理解的,虽然写法有很多种,但这是为了解决不同环境下的不同问题,我应该把懒汉和饿汉都手撕了,简单说明一下懒汉和饿汉的区别。当时让我吃惊的是,面试官告诉我,其实examplemode的写法有七种,甚至更多。我震惊得下巴都掉了。那时候觉得这个行业充满了挑战,充满了知识等着我去学习。果不其然,越学越有感触。废物,越学越觉得自己知道的不多,但是这条路还是要走下去的。让我们坚持下去。下面简单介绍一下单例模式。顾名思义,单例模式是唯一的实例。当前进程中,单例模式创建的类对象只有一个,比如生活中的太阳,只能有一个,所以只能有一个实例。这个例子在射箭之前用在后羿身上是不合适的,但是现在应该算是合适的了。另一个例子是写一个校园管理系统。只有一个校长的角色。最常见的也是大家认为最简单的一种,但其实这个模式的细节还是很多的,值得思考的点也很多。后面我们一起看各种写法的时候,大家记得带着你的思考和你的问题去学习单例模式的特点单例模式有以下特点:1.一个JVM中只有一个实例,构造函数是私有的,并且无法在外部创建实例2.提供一个publicget这种方法获取唯一实例的好处是什么:1.消除new操作,降低系统内存使用频率,减轻GC压力2.系统中有些类需要全局单例,比如spring中的controller,再比如humansun3。它避免了资源的重复占用,减少了内存开销。其实也有一些缺点:没有接口,非继承与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面发生了什么。为了实例化示例,让我告诉您要介绍的七种类型。大家印象中有饿汉式、懒人式线程不安全和安全版、DCL双检测锁模式线程不安全和安全版、静态内部类、枚举类,大家先熟悉一下。下面将一一介绍饿了么中国风和饿了么中国风。他们饿了,所以他们吃得更早,也就是说,他们被创造得更早。单例将随着JVM的启动而初始化。正是因为在加载这个类的时候就完成了单例的实例化,不存在所谓的线程安全问题,是线程安全的,相应的缺点就是没有达到懒加载的效果。如果创建的单例类一直不用的话,会造成资源浪费。其实在实际开发中,即使知道会用到,我们一般也不会使用这种机制,因为如果单例对象很多,会影响启动速度。懒加载机制在资源节约型开发中的很多思想也是用到了懒加载。只有在实际使用的时候,才允许占用相应的资源/***饿了么中国风:通过classloader机制避免了多线程同步问题。加载时实例化完成*优点:写法简单,类加载时实例化完成,避免了线程同步的问题*缺点:达不到延迟加载的效果,一直不用可能会浪费资源*适用场景:*/publicclassHungrySingleton{//1,构造函数私有化privateHungrySingleton(){}//2.在类内部创建对象的实例privatefinalstaticHungrySingletondayu=newHungrySingleton();//3.提供一个返回类内部实例的静态方法privatestaticHungrySingletongetInstance(){returndayu;}}lazymanLazystyle(threadunsafe,threadsafe)懒人风格,很懒。启动的时候不会创建单例对象,真正用到的时候才会加载这些东西。之所以加入懒惰风格,大概是采用了懒加载的思想。下面看懒人风格的代码/***懒人风格*缺点:线程不安全,工作中一般不用*/publicclassNotSafeLazySingleton{//constructorprivateNotSafeLazySingleton(){}//暂时不加载实例privatestaticNotSafeLazySingletondayu;/***存在线程安全问题*线程A判断括号内dayu==null后进入括号内,*此时线程B获得执行权,判断==null为也是如此,所以也进入*此时两个线程有??两个dayu对象*@return*/publicstaticNotSafeLazySingletongetInstance(){if(dayu==null){dayu=newNotSafeLazySingleton();}returndayu;}}其实就是一个有过线程过多经验的小伙伴应该很快看出上面的lazy风格存在线程安全问题。当线程A执行if(dayu==null)这一行时,判断为空,true进入括号。这时候线程A的时间片用完了,到了线程B执行的时间,所以会判断为空。线程B在括号内创建一个NotSafeLazySingleton对象。在内部,线程A还会创建一个NotSafeLazySingleton对象GG,这不是我们想要的效果,这不属于单例模式,所以在多线程的情况下存在这种安全问题。有问题自然要解决咯,可能有些朋友也想到了。如果有线程安全问题,那就加上线程安全关键字synchronized来解决,于是就有了下面的代码,我们在函数中加入了synchronized关键字,但是这样会造成效率极低。所有调用该方法使用单例对象的地方都需要排队阻塞,直到锁被释放。在多线程的情况下,效率会迅速降低/***Lazy安全写法*缺点:synchronized关键字导致方法效率低,效率极低*优点:线程安全*适用场景:实际开发不推荐*/publicclassSafeLazySingleton{//constructorprivateprivateSafeLazySingleton(){}//暂时不加载实例privatestaticSafeLazySingletondayu;/***synchronized会导致所有通过该方法获取的对象都排队*/publicstaticsynchronizedSafeLazySingletongetInstance(){if(dayu==null){dayu=newSafeLazySingleton();}returndayu;}}所有调用该方法使用单例对象的地方都需要排队阻塞,直到锁释放。在多线程的情况下,效率会迅速降低,所以下面的改进方法只是锁定了部分代码,看下面的代码/***意在对SafeLazySingelton做一个改进,因为效率locking整个方法太low了*但是这样还是不能起到线程同步的作用和NotSafeLazySingelton类似只要线程进入==null*此时另一个线程获得CPU分配的时间片就会出现多个对象*/publicclassNotSafeLaySingleton2{//ConstructorprivateNotSafeLaySingleton2(){}//暂时不加载实例privatestaticNotSafeLaySingleton2dayu;/***@return*/publicstaticNotSafeLaySingleton2getInstance(){if(dayu==null){synchronized(NotSafeLaySingleton2.class){dayu=newNotSafeLaySingleton2();}}returndayu;}}上面的代码有没有问题??不知道聪明的小伙伴看完上面的代码你心里是怎么想的发现事情并没有那么简单,问题也找到了。是的,上面的改进方法看似可以达到更高的效率,但是会带来多线程的问题。线程A判断dayu==null,进入括号。NotSafeLaySingleton2的锁还没有拿到,时间片用完了。这时线程B也判断发现dayu==null也成立,此时也会进入括号。假设线程B获得了锁,创建了一个NotSafeLaySingleton2对象。执行后释放锁线程A拿到锁,会重新创建一个对象,所以有很多情况。首先在方法层面添加了synchronized,解决了并发问题,但是带来了效率问题,所以为了提高效率,在内部添加,但是添加内部有相应的线程安全问题。说了这么多,就是引出我们下面线程安全DCL的单例模式,看看double-checked锁模式DCL-doublechechkedlocking(threadsafety)是怎么写的。上面这个其实属于Singlechecklockmode,我之所以这样命名是因为只检查一个地方的lock,这也带来了多线程的问题,于是就有了下面这种doublecheck情况的singletonmode,一起来看看,getabatch/***DoubleCheckSingleton:getabatch*优点:线程安全的懒加载效率比较好*使用场景:实际开发中用的比较多*/publicclassDoubleCheckSingleton{privatestaticvolatileDoubleCheckSingletondayu;privateDoubleCheckSingleton(){}/***解决了线程安全的问题,也解决了延迟加载的问题*@return*/publicstaticDoubleCheckSingletongetInstance(){if(dayu==null){synchronized(DoubleCheckSingleton.class){if(dayu==null){dayu=newDoubleCheckSingleton();}}}returndayu;}}上面进入data==null内部后会再次判断是否仍然等于empty,很好的解决了多线程的问题这种DCL单例模式比较常见使用d在工作中。有效解决了高并发下单例模式的问题。静态内部类静态内部类加载单例。类加载机制保证了线程安全,还有一个好处,延迟加载,只有在调用getInstance的时候才会加载内部类,才会创建这个对象。存在线程安全问题/***静态内部类加载单例*优点:类加载机制保证线程安全懒加载只调用getInstance加载内部类*适用场景:*/publicclassStaticInnerClassSingleton{privateStaticInnerClassSingleton(){}/***1.加载外部类时,不会立即加载内部*2。调用getInstance方法时只会加载一次,没有线程安全*/privatestaticclassSingletonInstance{privatestaticfinalStaticInnerClassSingletondayu=newStaticInnerClassSingleton();}//返回静态内部类中的对象publicstaticStaticInnerClassSingletongetInstance(){returnSingletonInstance.dayu;}}枚举类枚举类也可以作为单例模式,也很简单。EffectiveJava作者JoshBloch提倡的单例实现就是这样,这种无线程安全问题也可以防止反序列化重新创建新对象Object*EffectiveJava作者JoshBloch提倡的方法*/publicclassEnumSingleton{publicstaticvoidmain(String[]args){//instance和instance2是同一个对象singletoninstance=Singleton.INSTANCE;Singletoninstance2=Singleton.INSTANCE;}enumSingleton{INSTANCE;}}总结设计模式应该属于面试频率高的,单例pattern是最简单的设计模式,或者说是最常见的设计模式之一。看完这篇文章,大家应该知道单例模式的各种写法了。知道了各种优缺点和对应的使用场景,我们来思考一个问题,为什么要用单例模式,要用静态方法,两者其实都可以达到我们加载的最终目的,只不过其中一个是基于对象的,还有一种是属于面向对象的,像很多情况一样,我们也可以通过普通的编码来实现,但是我们引入了设计模式,更好的体现了编程思想。如果一个方法确实与其类的实例对象无关,那么它应该是静态的,否则应该是非静态的。如果我们需要使用非静态方法,但是在创建类对象时,我们只需要维护一个实例,不想创建多个不同的实例。使用单例模式
