zoukankan      html  css  js  c++  java
  • 设计模式(2)--Singleton(单例模式)--创建型

    1.模式定义:

      单例模式确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

    2.模式特点:

     (1)单例类只能有一个实例。
     (2)单例类必须自己创建自己的唯一实例。
     (3)单例类必须给所有其他对象提供这一实例。

    3.使用场景:

      定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
    (1)需要频繁实例化然后销毁的对象。
    (2)创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
    (3)有状态的工具类对象。
    (4)频繁访问数据库或文件的对象。
    (5)以及其他我没用过的所有要求只有一个对象的场景。

    4.模式实现(八种方式):

      (1)饿汉式(静态常量)[可用]

    public class Singleton { 

      private final static Singleton INSTANCE = new Singleton();
      private Singleton(){}
      public static Singleton getInstance(){
        return INSTANCE;
      }
    }
    优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
    缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

      (2)饿汉式(静态代码块)[可用]

    public class Singleton { 
      private static Singleton instance;
      static {
        instance = new Singleton();
      }
      private Singleton() {}
      public Singleton getInstance() {
        return instance;
      }
    }
    这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

      (3)懒汉式(线程不安全)[不可用]

    public class Singleton {
      private static Singleton singleton;
      private Singleton() {}
      public static Singleton getInstance() {
        if (singleton == null) {
          singleton = new Singleton();
        }
        return singleton;
      }
    }
    这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

      (4)懒汉式(线程安全,同步方法)[不推荐用]

    public class Singleton {
      private static Singleton singleton;
      private Singleton() {}
      public static synchronized Singleton getInstance() {
        if (singleton == null) {
          singleton = new Singleton();
        }
        return singleton;
      }
    }
    优点:解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
    缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

      (5)懒汉式(线程安全,同步代码块)[不可用]

    public class Singleton { 
      private static Singleton singleton;
      private Singleton() {}
      public static Singleton getInstance() {
        if (singleton == null) {
          synchronized (Singleton.class) {
            singleton = new Singleton();
          }
        }
        return singleton;
      }
    }
    由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

      (6)双重检查[推荐用]

    public class Singleton { 
      private static volatile Singleton singleton;
      private Singleton() {}
      public static Singleton getInstance() {
        if (singleton == null) {
          synchronized (Singleton.class) {
            if (singleton == null) {
              singleton = new Singleton();
            }
          }
        }
        return singleton;
      }
    }
    Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
    优点:线程安全;延迟加载;效率较高。

      (7).静态内部类[推荐用]

    public class Singleton { 
      private Singleton() {}
      private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
      }
      public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
      }
    }
    这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
    类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
    优点:避免了线程不安全,延迟加载,效率高。

      (8).枚举[推荐用]

    public enum Singleton { 
      INSTANCE;
      public void whateverMethod() { }
    }
    借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。
     

    5.优缺点:

    (1)单例模式的优点:

    [1]在内存中只有一个对象,节省内存空间。
    [2]避免频繁的创建销毁对象,可以提高性能。
    [3]避免对共享资源的多重占用。
    [4]可以全局访问。
    [5]灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。
    [6]实例控制:会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。

    (2)单例模式的缺点:

    [1]开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
    [2]可能的开发混淆:使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
    [3]对象生存期:不能解决删除单个对象的问题。

    6.注意事项

    (1)只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
    (2)不要做断开单例类对象与类中静态引用的危险操作。
    (3)多线程使用单例使用共享资源时,注意线程安全问题。

    7.相关问题

    (1)单例模式的对象长时间不用会被jvm垃圾收集器收集吗

             除非人为地断开单例中静态引用到单例对象的联接,否则jvm垃圾收集器是不会回收单例对象的。

    (2)在一个jvm中会出现多个单例吗

              在分布式系统、多个类加载器、以及序列化的的情况下,会产生多个单例,这一点是无庸置疑的。那么在同一个jvm中,会不会产生单例呢?使用单例提供的getInstance()方法只能得到同一个单例,除非是使用反射方式,将会得到新的单例。代码如下
    Class c = Class.forName(Singleton.class.getName());  
    Constructor ct = c.getDeclaredConstructor();  
    ct.setAccessible(true);  
    Singleton singleton = (Singleton)ct.newInstance();  
     
        这样,每次运行都会产生新的单例对象。所以运用单例模式时,一定注意不要使用反射产生新的单例对象。
     

     (3)懒汉式单例线程安全吗

              主要是网上的一些说法,懒汉式的单例模式是线程不安全的,即使是在实例化对象的方法上加synchronized关键字,也依然是危险的,但是笔者经过编码测试,发现加synchronized关键字修饰后,虽然对性能有部分影响,但是却是线程安全的,并不会产生实例化多个对象的情况。

       (4)单例模式只有饿汉式和懒汉式两种吗

              饿汉式单例和懒汉式单例只是两种比较主流和常用的单例模式方法,从理论上讲,任何可以实现一个类只有一个实例的设计模式,都可以称为单例模式。
     

    (5)单例类可以被继承吗

              饿汉式单例和懒汉式单例由于构造方法是private的,所以他们都是不可继承的,但是其他很多单例模式是可以继承的,例如登记式单例。
     

    (6)饿汉式单例好还是懒汉式单例好

              在java中,饿汉式单例要优于懒汉式单例。C++中则一般使用懒汉式单例。
     
     
  • 相关阅读:
    多线程008如何预防死锁
    多线程003volatile的可见性和禁止指令重排序怎么实现的
    多线程011线程池运行原理及复用原理
    多线程010为什么要使用线程池
    多线程009什么是守护线程
    多线程005创建线程有哪些方式
    多线程007描述一下线程安全活跃态问题,以及竞态条件
    多线程002ThreadLocal有哪些内存泄露问题,如何避免
    关于Tomcat的启动时机(精通Eclipse Web开发P40)
    乱侃~~~
  • 原文地址:https://www.cnblogs.com/yysbolg/p/7380568.html
Copyright © 2011-2022 走看看