单实例Singleton陈设模式可能是被讨论和运用的最广泛的一个陈设模式了,这可能也是口试中问得最众的一个陈设模式了。这个陈设模式浸要目标是想正正在整姿态统中只可显现一个类的实例。这样做当然是有必定的,比如你的软件的全事务署装备信歇,或者是一个Factory,或是一个主控类,等等。你希望这个类正正在整姿态统中只可显现一个实例。当然,作为一个技术负责人的你,你当然有权利通过运用非技术的手段来达到你的目标。比如:你正正在团队内部明文法则,“XX类只可有一个全事务署实例,如果某人运用两次以上,那么该人将被处于2000元的罚款!”(呵呵),你当然有权这么做。可是如果你的陈设的是东西是一个类库,或是一个需要提供给用户运用的API,恐怕你的这项法则将会失效。由于,你无权要求别人会那么做。以是,这就是为什么,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站希望通过运用技术的手段来完了这样一个目标的途理。

  本文会带着你深化整体Singleton的天下,当然,我会松手运用C++言语而改用Java 言语,由于运用Java这个言语可能更搪塞让我说明一些事情。

  Singleton的教育版本

  这里,我将直接给出一个Singleton的简单实现,由于我相信你曾经有这方面的一些基础了。银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站权且把这个版本叫做1.0版

Java代码
  1. // version 1.0  
  2. public class Singleton {  
  3.     private static Singleton singleton = null;  
  4.     private Singleton() {  }  
  5.     public static Singleton getInstance() {  
  6.         if (singleton== null) {  
  7.             singleton= new Singleton();  
  8.         }  
  9.         return singleton;  
  10.     }  
  11. }  

  正正在上面的实例中,我想说明下面几个Singleton的特性:(下面这些东西可能是尽人皆知的,没有什么新鲜的)

  1. 私有(private)的构制函数,表明这个类是不可能形成实例了。这浸要是怕这个类会有众个实例。

  2. 即然这个类是不可能形成实例,那么,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站需要一个静态的格事务署让其形成实例:getInstance()。帮理这个方法是正正在new自己,由于其可以拜访私有的构制函数,以是他是可以保证实例被创建出来的。

  3. 正正在getInstance()中,先做坚决是否已形成实例,如果已形成则直接返回,不然创建实例。

   4. 所形成的实例保存正正在自己类中的私有成员中。

  5. 银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站取实例时,只需要运用Singleton.getInstance()就行了。

  当然,如果你以为知途了上面这些事情后就学成了,那得给你当头棒喝一下了,事情远远没有那么简单。

  Singleton的执行版本

  上面的这个程序存正正在比拟严浸的问题,由于是全事务署性的实例,以是,正正在众线程情况下,所有的全事务署共享的东西都会变得非常的危险,这个也往往,正正在众线程情况下,如果众个线程同时调用getInstance()的话,那么,可能会有众个历程同时通过 (singleton== null)的前提反省,于是,众个实例就创建出来,而且很可能制成内存泄露问题。嗯,熟习众线程的你一定会说——“银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站需要线程互斥或同步”,没错,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站需要这个事情,于是银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站的Singleton升级成1.1版,如下所示:

Java代码
  1. // version 1.1  
  2. public class Singleton  
  3. {  
  4.     private static Singleton singleton = null;  
  5.     private Singleton() {  }  
  6.     public static Singleton getInstance() {  
  7.         if (singleton== null) {  
  8.             synchronized (Singleton.class) {  
  9.                 singleton= new Singleton();  
  10.             }  
  11.         }  
  12.         return singleton;  
  13.     }  
  14. }  

   嗯,运用了Java的synchronized方法,看起来不错哦。应该没有问题了吧?!错!这还是有问题!为什么呢?前面曾经说过,如果有众个线程同时通过(singleton== null)的前提反省(由于他们并行运行),固然银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站的synchronized方法会帮帮银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站同步所有的线程,让银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站并行线程变成串行的一个一个去new,那不还是往往的吗?同样会显现很众实例。嗯,确实云云!看来,还得把那个坚决(singleton== null)前提也同步起来。于是,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站的Singleton再次升级成1.2版本,如下所示:

Java代码
  1. // version 1.2  
  2. public class Singleton  
  3. {  
  4.     private static Singleton singleton = null;  
  5.     private Singleton()  {  }  
  6.     public static Singleton getInstance()  {  
  7.         synchronized (Singleton.class) {  
  8.             if (singleton== null) {  
  9.         singleton= new Singleton();  
  10.             }  
  11.          }  
  12.         return singleton;  
  13.     }  
  14. }  

   不错不错,看似很不错了。正正在众线程下应该没有什么问题了,不是吗?的确是这样的,1.2版的Singleton正正在众线程下的确没有问题了,由于银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站同步了所有的线程。只不过嘛……,什么?!还不行?!是的,还是有点小问题,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站本来只是想让new这个操作并行就可以了,现正正在,只要是进入getInstance()的线程都得同步啊,帮理,创建对象的行为只有一次,后面的行为全是读取那个成员变量,这些读取的行为不需要线程同步啊。这样的作法以为非常分外啊,为了一个初始化的创建行为,居然让银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站达上了所有的读操作,严浸影响后续的功能啊!

  还得改!嗯,看来,正正在线程同步前还得加一个(singleton== null)的前提坚决,如果对象曾经创建了,那么就不需要线程的同步了。OK,下面是1.3版的Singleton。

Java代码
  1. // version 1.3  
  2. public class Singleton  
  3. {  
  4.     private static Singleton singleton = null;  
  5.     private Singleton()  {    }  
  6.     public static Singleton getInstance() {  
  7.         if (singleton== null)  {  
  8.             synchronized (Singleton.class) {  
  9.                 if (singleton== null)  {  
  10.                     singleton= new Singleton();  
  11.                 }  
  12.             }  
  13.         }  
  14.         return singleton;  
  15.     }  
  16. }  

  以为代码开始变得有点罗嗦和复杂了,不过,这可能是最不错的一个版本了,这个版本又叫“双浸反省”Double-Check。下面是说明:

   1. 第一个前提是说,如果实例创建了,那就不需要同步了,直接返回就好了。

   2. 不然,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站就开始同步线程。

  3. 第二个前提是说,如果被同步的线程中,有一个线程创建了对象,那么别的线程就不用再创建了。

  相当不错啊,干得非常美丽!请大家为银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站的1.3版起立鼓掌!

  但是,如果你以为这个版本大攻告成,你就错了。

   浸要正正在于singleton = new Singleton()这句,这并非是一个原子操作,实情上正正在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 singleton 分配内存

  2. 调用 Singleton 的构制函数来初始化成员变量,形成实例

   3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)

  但是正正在 JVM 的即时编译器中存正正在指令浸排序的优化。也就是说上面的第二步和第三步的顺序是不行保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则正正在 3 执行杀青、2 未执行之前,被线程二抢占了,这时 instance 曾经是非 null 了(但却没有初始化),以是线程二会直接返回 instance,然后运用,然后顺理成章地报错。

  对此,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站只需要把singleton声明成 volatile 就可以了。下面是1.4版:

Java代码
  1. // version 1.4  
  2. public class Singleton  
  3. {  
  4.     private volatile static Singleton singleton = null;  
  5.     private Singleton()  {    }  
  6.     public static Singleton getInstance()   {  
  7.         if (singleton== null)  {  
  8.             synchronized (Singleton.class) {  
  9.                 if (singleton== null)  {  
  10.                     singleton= new Singleton();  
  11.                 }  
  12.             }  
  13.         }  
  14.         return singleton;  
  15.     }  
  16. }  

  运用 volatile 有两个功用:

   1)这个变量不会正正在众个线程中存正正在复本,直接从内存读取。

  2)这个关键字会拦阻指令浸排序优化。也就是说,正正在 volatile 变量的赋值操作后面会有一个内存屏障(天生的汇编代码上),读操作不会被浸排序到内存屏障之前。

  但是,这个事情仅正正在Java 1.5版后有效,1.5版之前用这个变量也有问题,由于老版本的Java的内存模型是有缺陷的。

  Singleton 的简化版本

  上面的玩法实正正在是太复杂了,一点也不斯文,下面是一种更为斯文的格事务署:

  这种方法非常简单,由于单例的实例被声明成 static 和 final 变量了,正正在第一次加载类到内存中时就会初始化,以是创建实例本身是线程幽静的。

Java代码
  1. // version 1.5  
  2. public class Singleton  
  3. {  
  4.     private volatile static Singleton singleton = new Singleton();  
  5.     private Singleton()  {    }  
  6.     public static Singleton getInstance()   {  
  7.         return singleton;  
  8.     }  
  9. }  

   但是,这种玩法的最大问题是——当这个类被加载的时候,new Singleton() 这句话就会被执行,就算是getInstance()没有被调用,类也被初始化了。

   于是,这个可能会与银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站想要的行为不往往,比如,我的类的构制函数中,有一些事可能需要依赖于别的类干的一些事(比如某个装备文件,或是某个被其它类创建的资源),银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站希望他能正正在我第一次getInstance()时才被真正的创建。这样,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。

  好吧,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站还得绕一下:

  下面的这个1.6版是老版《Effective Java》中引荐的格事务署。

Java代码
  1. // version 1.6  
  2. public class Singleton {  
  3.     private static class SingletonHolder {  
  4.         private static final Singleton INSTANCE = new Singleton();  
  5.     }  
  6.     private Singleton (){}  
  7.     public static final Singleton getInstance() {  
  8.         return SingletonHolder.INSTANCE;  
  9.     }  
  10. }  

   上面这种格事务署,仍然运用JVM本身机制保证了线程幽静问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有观点拜访它,于是它只有正正在getInstance()被调用时才会真正创建;同时读取实例的时候不会进行同步,没有功能缺陷;也不依赖 JDK 版本。

  Singleton 斯文版本

Java代码
  1. public enum Singleton{  
  2.    INSTANCE;  
  3. }  

   居然用枚举!!看上去好牛逼,通过EasySingleton.INSTANCE来拜访,这比调用getInstance()方法简单众了。

  默认枚举实例的创建是线程幽静的,以是不需要担心线程幽静的问题。但是正正在枚举中的其他任何方法的线程幽静由程序员自己负责。还有防止上面的通过反射机制调用私用构制器。

  这个版本根蒂上拂拭了绝大普及的问题。代码也非常简单,实正正在无法不用。这也是新版的《Effective Java》中引荐的模式。

  Singleton的其它问题

  怎样?还有问题?!当然还有,请记住下面这条法则——“无论你的代码写得有众好,其只可正正在特定的范围内工作,超出这个范围就要出Bug了”,这是“陈式第一定理”,呵呵。你能想一想还有什么情况会让这个银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站上面的代码出问题吗?

  正正在C++下,我不是很好举例,但是正正在Java的情况下,嘿嘿,还是让银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站来看看下面的一些反例和一些别的事情的讨论(当然,有些反例可能属于钻牛角尖,可能有点学院派,不过也不排除其执行可能性,就算是提个醒吧):

  其一、Class Loader。不知途你对Java的Class Loader熟习吗?“类装载器”?!C++可没有这个东西啊。这是Java动态性的核心。顾名思义,类装载器是用来把类(class)装载进JVM的。JVM规范定义了两种样板的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。 正正在一个JVM中可能存正正在众个ClassLoader,每个ClassLoader具有自己的NameSpace。一个ClassLoader只可具有一个class对象样板的实例,可是折柳的ClassLoader可能具有相同的class对象实例,这时可能发生致命的问题。如ClassLoaderA,装载了类A的样板实例A1,而ClassLoaderB,也装载了类A的对象实例A2。逻辑上讲A1=A2,可是由于A1和A2来自于折柳的ClassLoader,它们执行上是绝对折柳的,如果A中定义了一个静态变量c,则c正正在折柳的ClassLoader中的值是折柳的。

  于是,如果咱们的Singleton 1.3版本如果面临着众个Class Loader会怎样样?呵呵,众个实例同样会被众个Class Loader创建出来,当然,这个有点牵强,不过他确实存正正在。难途银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站还要整出个1.4版吗?可是,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站怎样可能正正在我的Singleton类中操作Class Loader啊?是的,你根本不可能。正正在这种情况下,你能做的只有是——“保证众个Class Loader不会装载同一个Singleton”。

  其二、序例化。 如果银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站的这个Singleton类是一个关于银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站程序装备信歇的类。银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站需要它有序列化的功能,那么,当反序列化的时候,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站将无法控制别人不众次反序列化。不过,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站可以诈欺一下Serializable接口的readResolve()方法,比如:

Java代码
  1. public class Singleton implements Serializable  
  2. {  
  3.     ......  
  4.     ......  
  5.     protected Object readResolve()  
  6.     {  
  7.         return getInstance();  
  8.     }  
  9. }  

  其三、众个Java虚拟机。 如果银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站的程序运行正正在众个Java的虚拟机中。什么?众个虚拟机?这是一种什么样的情况啊。嗯,这种情况是有点分外,不过还是可能显现,比如EJB或RMI之流的东西。要正正在这种情况下避免众实例,看来只可通过良好的陈设或非技术来解决了。

  其四,volatile变量。 关于volatile这个关键字所声明的变量可以被看作是一种 “程度较轻的同步synchronized”;与 synchronized 块比拟,volatile 变量所需的编码较少,而且运行时开销也较少,可是它所能实现的功能也仅是synchronized的一限制。当然,如前面所述,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站需要的Singleton只是正正在创建的时候线程同步,而后面的读取则不需要同步。以是,volatile变量并不行帮帮银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站即能解决问题,又有好的功能。而且,这种变量只可正正在JDK 1.5+版后能力运用。

  其五、关于继承。 是的,继承于Singleton后的子类也有可能制成众实例的问题。不过,由于银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站早把Singleton的构制函数声明成了私有的,以是也就杜绝了继承这种事情。

  其六,关于代码浸用。也话银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站的编制中有很众个类需要用到这个模式,如果银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站正正在每一个类都中有这样的代码,那么就显得有点傻了。那么,银河文娱有哪些网站_云顶文娱场7610_bet9九州 网站是否可以运用一种方法,把这具模式抽象出去?正正在C++下这是很搪塞的,由于有模板和友元,还支持栈上分配内存,以是比拟搪塞一些(程序如下所示),Java下可能比拟复杂一些,醒目的你知途怎样做吗?

C++代码
  1. template class Singleton  
  2. {  
  3.     public:  
  4.         static T& Instance()  
  5.         {  
  6.             static T theSingleInstance; //假设T有一个protected默认构制函数  
  7.             return theSingleInstance;  
  8.         }  
  9. };  
  10.    
  11. class OnlyOne : public Singleton  
  12. {  
  13.     friend class Singleton;  
  14.     int example_data;  
  15.    
  16.     public:  
  17.         int GetExampleData() const {return example_data;}  
  18.     protected:  
  19.         OnlyOne(): example_data(42) {}   // 默认构制函数  
  20.         OnlyOne(OnlyOne&) {}  
  21. };  
  22.    
  23. int main( )  
  24. {  
  25.     cout << OnlyOne::Instance().GetExampleData() << endl;  
  26.     return 0;  
  27. }
除非特别注明,鸡啄米文章均为原创
转载请标明本文地址:http://www.sygjbus.cn/software/752.html
2017年8月11日
作家:鸡啄米 分类:软件开发 浏览: 评论:0