zoukankan      html  css  js  c++  java
  • Java二十三设计模式之------单例模式

    一、单例模式Singleton

    单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

    1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

    2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

    3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

    首先我们写一个简单的单例类:

     
    1. public class Singleton {  
    2.   
    3.     /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
    4.     private static Singleton instance = null;  
    5.   
    6.     /* 私有构造方法,防止被实例化 */  
    7.     private Singleton() {  
    8.     }  
    9.   
    10.     /* 静态工程方法,创建实例 */  
    11.     public static Singleton getInstance() {  
    12.         if (instance == null) {  
    13.             instance = new Singleton();  
    14.         }  
    15.         return instance;  
    16.     }  
    17.   
    18.     /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    19.     public Object readResolve() {  
    20.         return instance;  
    21.     }  
    22. }  


    这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字,如下:

     
    1. public static synchronized Singleton getInstance() {  
    2.         if (instance == null) {  
    3.             instance = new Singleton();  
    4.         }  
    5.         return instance;  
    6.     }  

    但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:

     
    1. public static Singleton getInstance() {  
    2.         if (instance == null) {  
    3.             synchronized (instance) {  
    4.                 if (instance == null) {  
    5.                     instance = new Singleton();  
    6.                 }  
    7.             }  
    8.         }  
    9.         return instance;  
    10.     }  

    似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的,看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:

    a>A、B线程同时进入了第一个if判断

    b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();

    c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。

    d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。

    e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

    不是很理解(Java在new对象时在堆中分配了空间,但初始化需要一定的时间,虚拟机继续执行其他任务,然后用到这个对象,尴尬发生了,对象没有初始化)

    1、instance = new Singleton();是分两步,先给Singleton分配空间(但是不一定初始化它),那么就可能出现在随后的调用instance 是出现空指针异常,进一步得出结论Java是不稳定的,你认为这个结论对吗?
    2、最主要一个问题在于你所说的理论自相矛盾:A离开synchronized后将instance 返回,B紧接着进入,B得到的instance可能出现不稳定,那么A得到的instance 也应该出现不稳定才对,因为B返回instance 的时间在A后面(同一个对象,你先拿到是初始化的,我后拿到反而变为未初始化了),那么在此得出与1相同的结论,java不稳定,对吗?

    所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化:

     
    1. private static class SingletonFactory{           
    2.         private static Singleton instance = new Singleton();           
    3.     }           
    4.     public static Singleton getInstance(){           
    5.         return SingletonFactory.instance;           
    6.     }   

    实际情况是,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式:

    1. public class Singleton {  
    2.   
    3.     /* 私有构造方法,防止被实例化 */  
    4.     private Singleton() {  
    5.     }  
    6.   
    7.     /* 此处使用一个内部类来维护单例 */  
    8.     private static class SingletonFactory {  
    9.         private static Singleton instance = new Singleton();  
    10.     }  
    11.   
    12.     /* 获取实例 */  
    13.     public static Singleton getInstance() {  
    14.         return SingletonFactory.instance;  
    15.     }  
    16.   
    17.     /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    18.     public Object readResolve() {  
    19.         return getInstance();  
    20.     }  
    21. }  

    其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。也有人这样实现:因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的:

    类中变量及方法的加载顺序

    父类-静态-变量
    父类 -  静态 - 初始化块
    子类-静态-变量
    子类 -  静态 - 初始化块
    父类-非静态-变量
    父类 - 非静态 - 初始化块
    父类-构造方法
    子类-非静态-变量
    子类- 非静态 - 初始化块
    子类-构造方法
    1. public class SingletonTest {  
    2.   
    3.     private static SingletonTest instance = null;  
    4.   
    5.     private SingletonTest() {  
    6.     }  
    7.   
    8.     private static synchronized void syncInit() {  
    9.         if (instance == null) {  
    10.             instance = new SingletonTest();  
    11.         }  
    12.     }  
    13.   
    14.     public static SingletonTest getInstance() {  
    15.         if (instance == null) {  
    16.             syncInit();  
    17.         }  
    18.         return instance;  
    19.     }  
    20. }  

    考虑性能的话,整个程序只需创建一次实例,所以性能也不会有什么影响。

    补充:采用"影子实例"的办法为单例对象的属性同步更新

    1. public class SingletonTest {  
    2.   
    3.     private static SingletonTest instance = null;  
    4.     private Vector properties = null;  
    5.   
    6.     public Vector getProperties() {  
    7.         return properties;  
    8.     }  
    9.   
    10.     private SingletonTest() {  
    11.     }  
    12.   
    13.     private static synchronized void syncInit() {  
    14.         if (instance == null) {  
    15.             instance = new SingletonTest();  
    16.         }  
    17.     }  
    18.   
    19.     public static SingletonTest getInstance() {  
    20.         if (instance == null) {  
    21.             syncInit();  
    22.         }  
    23.         return instance;  
    24.     }  
    25.   
    26.     public void updateProperties() {  
    27.         SingletonTest shadow = new SingletonTest();  
    28.         properties = shadow.getProperties();  
    29.     }  
    30. }  

    通过单例模式的学习告诉我们:

    1、单例模式理解起来简单,但是具体实现起来还是有一定的难度。

    2、synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。

    到这儿,单例模式基本已经讲完了,结尾处,笔者突然想到另一个问题,就是采用类的静态方法,实现单例模式的效果,也是可行的,此处二者有什么不同?

    首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)

    其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。

    再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。

    最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!

    以上内容引自于   https://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html

  • 相关阅读:
    tuple 元组及字典dict
    day 49 css属性补充浮动 属性定位 抽屉作业
    day48 选择器(基本、层级 、属性) css属性
    day47 列表 表单 css初识
    day 46 http和html
    day 45索引
    day 44 练习题讲解 多表查询
    day 40 多表查询 子查询
    day39 表之间的关联关系、 补充 表操作总结 where 、group by、
    day38 数据类型 约束条件
  • 原文地址:https://www.cnblogs.com/wuxinyiwu/p/8057897.html
Copyright © 2011-2022 走看看