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

    什么是单例

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

    主要解决:一个全局使用的类频繁地创建与销毁。

    何时使用:当您想控制实例数目,节省系统资源的时候。

    如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

    关键代码:构造函数是私有的。

    优点:

        1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例

        2.提供了对唯一实例的受控访问。

        3.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。

        4.避免对共享资源的多重占用。

    缺点:

        1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。

        2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。

        3.单例类的职责过重,在一定程度上违背了“单一职责原则”。

    单例创建方式

    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 singleton;
      
          private Singleton() {}
      
          public static synchronized Singleton getInstance() {
              if (singleton == null) {
                  singleton = new Singleton();
              }
              return singleton;
          }
      }

      优点:类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。                                                                       缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

    3. 静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
      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帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

      优点:避免了线程不安全,延迟加载,效率高。

    4. 枚举单例: 使用枚举实现单例模式 优点:实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。
      public class User {
          public static User getInstance() {
              return SingletonDemo04.INSTANCE.getInstance();
          }
      
          private static enum SingletonDemo04 {
              INSTANCE;
              // 枚举元素为单例
              private User user;
      
              private SingletonDemo04() {
                  System.out.println("SingletonDemo04");
                  user = new User();
              }
      
              public User getInstance() {
                  return user;
              }
          }
      
          public static void main(String[] args) {
              User u1 = User.getInstance();
              User u2 = User.getInstance();
              System.out.println(u1 == u2);
          }
      }
    5. 双重检测锁方式 
      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实例化对象。

      需要注意的是,private static volatile Singleton singleton需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

  • 相关阅读:
    C#操作AD及Exchange Server总结(二)
    YUV采样及存储格式
    windows主线程等待子线程退出卡死问题
    fastdb中的位图应用
    【转】浏览器的跨域访问
    【转】linux线程模型
    【转】STL空间配置器
    【转】C++ 内存分配(new,operator new)详解
    Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
    idea启动项目address localhost:1099 is already in use异常解决
  • 原文地址:https://www.cnblogs.com/wangzhanhua/p/10550820.html
Copyright © 2011-2022 走看看