zoukankan      html  css  js  c++  java
  • java 单例模式

    单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计大师并把把其称为设计模式之一。

    这里又不具体讲如何实现单例模式和介绍其原理(因为这方便的已经有太多的好文章介绍了),如果对单例模式不了解的可以先看下:http://terrylee.cnblogs.com/archive/2005/12/09/293509.html 。当然也可以自己搜索

    好多没怎么使用过的人可能会想,单例模式感觉不怎么用到,实际的应用场景有哪些呢?以下,我将列出一些就在咱们周边和很有意义的单例应用场景。

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

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

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

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

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

    6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

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

    8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

    9. HttpApplication 也是单位例的典型应用。熟悉ASP.NET(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.

    总结以上,不难看出:

      单例模式应用的场景一般发现在以下条件下:

      (1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。

      (2)控制资源的情况下,方便资源之间的互相通信。如线程池等。

    设计模式(一):单例模式(Singleton Pattern)

           单例模式(Singleton Pattern)是设计模式中比较常用的一种,下面来总结单例模式的知识,包括:

           1、理解什么是单例模式、单例模式有什么优点/缺点、单例模式的应用场景;

           2、再来看看Java单例模式的6种代码实现方式、每种实现方式有什么需要注意的;

           3、后面再来了解Java单例模式其他值得关注的地方,如比较静态方法、以及Java反射、反序列化、垃圾回收的影响等。

    1、什么是单例模式

    1-1、模式理解

          保证一个类仅有一个实例,并提供一个访问它的全局访问点。

           UML结构图:

           模式角色:

          一个类使用了单例模式,称该类为单例类,如图中的Singleton。

           单例模式三要点:

          (1)、单例类只能有一个实例

          这是最基本的,真正做到整个系统中唯一并不容易,通常还要考虑反射破坏、序列化/反序列化、对象垃圾回收等问题。

          (2)、单例类必须自己创建自己的唯一实例

          通常给实例构造函数protected或private权限。

          (3)、单例类必须给所有其他对象提供这一实例

          通常定义静态方法getInstance()返回。

    1-2、特点

           优点:

           (1)、提供了对唯一实例的受控访问,避免对资源的多重占用。

           (2)、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。

           (3)缩小名空间,避免全局变量污染空间,但比类操作更灵活。

           缺点:

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

           (2)、 单例类的职责过重,在一定程度上违背了"单一职责原则"。

          因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

           所以也不应过多使用单例模式。

    1-3、应用

           单例模式是一种对象创建型模式,用来编写一个类,在整个应用系统中只能有该类的一个实例对象。

           常见应用场景:

               线程池、缓存、日志、配置文件、打印机/显卡等硬件设备的驱动程序对象等等。

           JDK中的一些应用:

           java.lang.Runtime#getRuntime()

          java.text.NumberFormat#getInstance()

          java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()

    面向对象的设计模式可以说是一种“套路”,是不分编程语言的,而后面主要以Java言语为主。

    2、单例模式实现示例

          Java单例模式的实现有多种方式,使用Java实现如下所示。

    2-1、饿汉式(简单可用)

           Lazy 初始化:否;

           多线程安全:是;

           描述:

          这种方式比较常用,它基于JVM的类加载器机制避免了多线程的同步问题,对象在类装载时就实例化,所以称为饿汉式。

           优点:没有加锁,执行效率会提高。

           缺点:没有Lazy初始化,可能有时候不需要使用,浪费内存。

           代码实例:

    [java] view plain copy
     
    1. public class Singleton {  
    2.   
    3.     private static Singleton instance = new Singleton();  
    4.      
    5.     private Singleton (){}  
    6.       
    7.     public static Singleton getInstance() {  
    8.         return instance;  
    9.   
    10.     }  
    11.   
    12. }  

    2-2、懒汉式(线程不安全,不可用)

          Lazy 初始化:是;

          多线程安全:否;

          描述:

          能够在getInstance()时再创建对象,所以称为懒汉式。这种实现最大的问题就是不支持多线程。因为没有加锁同步。

          代码实例:

    [java] view plain copy
     
    1. public class Singleton {  
    2.   
    3.     private static Singleton instance;  
    4.   
    5.     private Singleton (){}  
    6.        
    7.     public static Singleton getInstance() {  
    8.   
    9.         if (instance == null) {  
    10.             instance = new Singleton();  
    11.         }  
    12.   
    13.         return instance;  
    14.     }  
    15.   
    16. }  

    2-3、同步方法的懒汉式(同步方法效率低,不推荐)

          Lazy 初始化:是

          多线程安全:是

          描述:

          除第一次使用,后面getInstance()不需要同步;每次同步,效率很低。

          代码实例:

    [java] view plain copy
     
    1. public class Singleton {  
    2.   
    3.     private static Singleton instance;  
    4.   
    5.     private Singleton (){}  
    6.   
    7.     public static synchronized Singleton getInstance() {  
    8.   
    9.         if (instance == null) {  
    10.             instance = new Singleton();  
    11.         }  
    12.   
    13.         return instance;  
    14.     }  
    15.   
    16. }  

    2-4、双重校验锁(可用)

          Lazy 初始化:是;

          多线程安全:是;

          描述:

          这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

          实例变量需要加volatile 关键字保证易变可见性,JDK1.5起才可用。

          代码实例:

    [java] view plain copy
     
    1. public class Singleton {  
    2.   
    3.     private volatile static Singleton singleton;  
    4.   
    5.     private Singleton (){}  
    6.   
    7.     public static Singleton getSingleton() {  
    8.   
    9.         if (singleton == null) {  
    10.             synchronized (Singleton.class) {  
    11.                 if (singleton == null) {  
    12.                     singleton = new Singleton();  
    13.                  }  
    14.              }  
    15.         }  
    16.   
    17.         return singleton;  
    18.     }  
    19.   
    20. }  

    2-5、静态内部类(推荐)

          Lazy 初始化:是;

          多线程安全:是;

          描述:

          同样利用了JVM类加载机制来保证初始化实例对象时只有一个线程,静态内部类SingletonHolder 类只有第一次调用 getInstance 方法时,才会装载从而实例化对象。

          代码实例:

    [java] view plain copy
     
    1. public class Singleton {  
    2.   
    3.     private static class SingletonHolder {  
    4.   
    5.        private static final Singleton INSTANCE = new Singleton();  
    6.     }  
    7.   
    8.     private Singleton (){}  
    9.   
    10.     public static final Singleton getInstance() {  
    11.   
    12.         return SingletonHolder.INSTANCE;  
    13.     }  
    14.   
    15. }   

    2-6、枚举(《Effective Java》推荐,不常见)

          Lazy 初始化:否;

          多线程安全:是;

          描述:

          从Java1.5开始支持enum特性;无偿提供序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。

          不过,用这种方式写不免让人感觉生疏,这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。

          代码实例:

    [java] view plain copy
     
    1. public enum Singleton {  
    2.   
    3.     //定义一个枚举的元素,就代表Singleton实例  
    4.     INSTANCE;  
    5.   
    6.     /* 
    7.     **假如还定义有下面的方法,调用:Singleton.INSTANCE.doSomethingMethod(); 
    8.     */  
    9.   
    10.     public void doSomethingMethod() {  
    11.   
    12.     }  
    13.   
    14. }   

    2-7、小结

          以上6种单例实现方式,不是线程安全的不能用,至于是否需要延时加载,看情况而定。

          一般情况下,使用最基本、最简单的第一种饿汉式就行了(JDK中有不少使用该种方式),需要延时加载的使用静态内部类方式,需要高安全性的可以使用第6种枚举方式

    3、其他关注点

    3-1、单例模式VS静态类(静态属性/方法)

          把类中所有属性/方法定义成静态也可以实现"单例"。

          那为什么需要用"NEW"单例模式,在而不把类中所有属性/方法定义成静态的?

          静态类不用实例化就可以使用,虽然使用比较方便,但失去了面向对象的一些优点,适用于一些过程简单且固定、不需要扩展变化、不需要维护任何状态的类方法,如java.lang.Math,里面每种计算方法基本都是固定不变的。

          单例模式保证一个类对象实例的唯一性,有面向对象的特性,虽然扩展不容易,但还是可以被继承(protected权限的构造方法)、重写方法等。

    3-2、Java反射攻击破坏单例模式

          上面6种Java单例模式实现方式除枚举方式外,其他的给实例构造函数protected或private权限,依然可以通过相关反射方法,改变其权限,创建多个实例,如下:

    [java] view plain copy
     
    1. public class Test {  
    2.   
    3.     public static void main(String args[]) {  
    4.        
    5.         Singleton singleton = Singleton.getInstance();  
    6.   
    7.        try {  
    8.   
    9.             Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();  
    10.             constructor.setAccessible(true);  
    11.             Singleton singletonnew = constructor.newInstance();  
    12.             System.out.println(singleton == singletonnew);  
    13.   
    14.         } catch (Exception e) {  
    15.   
    16.         }       
    17.     }  
    18. }  

    输出结果:false

          可以给构造函数加上判断,限制创建多个实例,如下:

    [java] view plain copy
     
    1. private Singleton() {  
    2.   
    3.     if (null != Singleton.singleton) {  
    4.   
    5.         throw new RuntimeException();  
    6.     }  
    7. }  

    3-3、反序列化攻击破坏单例模式

          很多语言、框架都支持对象的序列化,对象序列化后再进行存储或传输,以获得更好的效率,之后再反序列化得到同样的对象信息。

          同样,前面6种Java单例模式实现方式除枚举方式外,其他方式用一样的序列化数据,可以多次反序列出多个不同的实例对象。

          对于Java语言提供的序列化/反序列化机制,需要单例类实现java.io.Serializable接口;而在在反序列化时会调用实例的readResolve()方法,只要加入该方法,并在方法中指定返回单例对象,就不会再新建一个对象,如下:

    [java] view plain copy
     
    1. private Object readResolve() {  
    2.     return Singleton.singleton;  
    3. }  

          另外,最好还要确保该类的所有实例域都为基本类型,或者是transient的。否则,还是可能受到攻击破坏。

          更多信息,可以参考:

          《Effective Java》第二版 第77条:对于实例控制,枚举类型优先于readResolve

    3-4、单例模式中的单例对象会不会被垃圾回收?

          对于JDK1.2后的JVM HotSpot来说,判断对象可以回收需要经过可达性分析,由于单例对象被其类中的静态变量引用,所以JVM认为对象是可达的,不会被回收。

          另外,对于JVM方法区回收,由堆中存在单例对象,所以单例类也不会被卸载,其静态变量引用也不会失效。

    3-5、多JVM/ClassLoader的系统使用单例类

          不同ClassLoader加载同一个类,对类本身的对象(Singleton.class)来说是不一样的,所以可以创建出不同的单例对象,对不同JVM的情况更是如此,这些在JavaEE开发中还是比较常见。

          所以,在多JVM/ClassLoader的系统使用单例类,需要注意单例对象的状态,最好使用无状态的单例类。

    3-6、Spring(IOC框架)实现的单例    

          Spring的一个核心功能控制反转(Inversion of Contro,IOC),或称依赖注入(dependency injection ,DI):
          高层模块通过接口编程,然后通过配置Spring的XML文件或注解来注入具体的实现类(Bean)。
          这样的好处的很容易扩展,想要更换其他实现类时,只需要修改配置就可以了。
          其功能是通过IOC容器来实现,其默认生成的Bean是单例的:
          在整个应用中(一般只用一个IOC容器),只创建Bean的一个实例,多次注入同一具体类时都是注入同一个实例。
          IOC容器来实现过程简述如下:
          当需要注入Bean时,IOC容器首先解析配置找到具体类,然后判断其作用域(@Scope注解);
          如果是默认的单例@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON),则查找容器中之前有没有为其创建了Bean实例;

          如果有则直接注入该Bean实例,如果没有生成一个放到容器中保存(ConcurrentHashMap -- map.put(bean_id, bean)),再注入。

          注:其中解析配置查找具体类、生成Bean实例和注入过程都是通过Java反射机制实现的。

          从上面可以了解到,Spring实现的单例和我们所说的单例设计模式不是一个概念:
          前者是IOC容器通过Java反射机制实现,后者只是一种编程方法(套路)。
          但总的来说,它们都可以实现“单例”。

    4、总结

          单例模式:

          (1)、单例模式可以在一些应用场景带来很好的效果,但不能滥用,因为单例模式并不是一种很好的模式。

          (2)、单例模式有多种实现方式,没有特殊要求的,用最基本、最简单的饿汉式,需要延时加载的使用静态内部类方式,需要高安全性的可以使用枚举方式;

          (3)、对其他关注点应有所了解,有时间可以深入探究,扩展知识面。

     

          到这里,我们对单例模式有了一个大体的了解,后面我们将了解其他的设计模式......

  • 相关阅读:
    Texture转Texture2D
    虚拟化 -- kvm简介
    虚拟化
    数据库
    openstack共享组件(1)------ NTP 时间同步服务
    openstack
    Linux基础命令
    第七章 Python文件操作
    第六章 Python流程控制
    老吴Python宝典之——Python字典&集合(第五章 )
  • 原文地址:https://www.cnblogs.com/wangyage/p/7290521.html
Copyright © 2011-2022 走看看