设计模式(4) ------- 单例模式

设计模式(4) ——- 单例模式

  • 这几条比较忙,都没时间码代码了。 俗话说:三天不打上房揭瓦。今天必须写一写

  • 继续设计模式的学习,本篇简单介绍一下单例模式

概述

  • 单例模式的由来(作用)

    很多时候,我们需要在应用中保存一个唯一的实例。比如,后台服务程序需要一个全局的计数器。通俗的讲,单例模式实现的就是使一个类的实例对象唯一,实例对象都指向的同一个地址。

  • 单例模式的实现

    很明显,要想保证一个类的实例唯一就必须把该类的构造方法私有化,以使在外部不能访问,从而为实现单例提供基本保证。接下来,三种实现方式:懒汉式、饿汉式、登记式。这里主要使用的是前两种方法。废话少说,直接上代码。

    • 懒汉式

      package 单例模式;
      
      /**
      * 懒汉式
      * @author by kissx on 2017/1/30.
      */
      public class LazySingleton {
        private static LazySingleton singleton = null;
      
        private LazySingleton(){}
      
        /*
        //实现同步方法一
        public static LazySingleton getInstance(){
            synchronized (LazySingleton.class){
                if (singleton == null)
                    singleton = new LazySingleton();
                return singleton;
            }
        }
        //*/
        //实现同步方法二
        public synchronized LazySingleton getInstance(){
            if (singleton == null)
                singleton = new LazySingleton();
            return singleton;
        }
      }

      说明:synchronized 关键字是用来实现同步的

    • 饿汉式

      package 单例模式;
      
      /**
      * 饿汉式
      * @author by kissx on 2017/1/30.
      */
      public class EagerSingleton {
        private static final EagerSingleton singleton = new EagerSingleton();
      
        private EagerSingleton() {}
      
        public static EagerSingleton getInstance(){
            return singleton;
        }
      }

      说明:饿汉式与懒汉式的区别在于:它们加载 完成 单例类的时机不同,懒汉式是在需要的时候(即获取此类的时候)才加载 完成,饿汉式是在一开始就加载 完成。但是,懒汉式需要考虑线程安全,而饿汉式在根本上就能保证线程安全。

    • 登记式(利用缓存技术)

      package 单例模式;
      
      import java.util.HashMap;
      import java.util.Map;
      
      /**
      * 登记式
      * 此处的登记式单例只是针对该类,而需要针对多个类时可以使用反射完成
      * @author by kissx on 2017/1/30.
      */
      public class RegSingleton {
        private static final String KEY = "ONE";
        private static Map<String,RegSingleton> map = new HashMap<>();
      
        static{
            RegSingleton regSingleton = new RegSingleton();
            map.put(KEY,regSingleton);
        }
      
        private RegSingleton(){}
      
        public synchronized static RegSingleton getInstance(String key){
            map.computeIfAbsent(key, k -> new RegSingleton());
            return map.get(key);
        }
      
        public synchronized static RegSingleton getInstance(){
            return getInstance(KEY);
        }
      }

      说明:不常用,了解一下就可以,感兴趣的可以查一下 java 的 缓存技术

举例说明

  • 案例说明

    在 Java 中经常需要读取配置文件的内容,在很多应用项目中都有与应用相关的配置文件,这些配置文件多是由项目开发人员自定义的,在里面定义一些应用需要的参数数据。在实际的项目中,这种配置文件采用 XML 格式,当然也有采用 properties 文件。这里我们采用 properties 文件。如何创建一个类来实现读取配置文件。

  • 代码

    package 单例模式;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    /**
    * 单例模式的优化实现
    * @author by kissx on 2017/1/30.
    */
    public class AppConfig {
      private String parameterA;
      private String parameterB;
    
      private static class AppConfigHolder{
          private static AppConfig appConfig = new AppConfig();
      }
    
      private AppConfig(){
          Properties properties = new Properties();
          try(InputStream in = AppConfig.class.getResourceAsStream("AppConfig.properties")){
              properties.load(in);
              parameterA = properties.getProperty("paramA");
              parameterB = properties.getProperty("paramB");
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
    
      public static AppConfig getInstance(){
          return AppConfigHolder.appConfig;
      }
    
      public String getParameterA() {
          return parameterA;
      }
    
      public String getParameterB() {
          return parameterB;
      }
    
      public static void main(String[] args) {
          AppConfig appConfig = AppConfig.getInstance();
          AppConfig appConfig1 = AppConfig.getInstance();
          System.out.println("A: " + appConfig.getParameterA());
          System.out.println("B: " + appConfig.getParameterB());
          System.out.println(appConfig.toString() + " : " + appConfig1.toString());
      }
    }

    说明:这里已经创建了一个 AppConfig.properties 配置文件,内容如下
    还有这里实现单例模式使用了新方式,具体见下面优化中的方案二。

    paramA=a
    paramB=b

单例模式优化

上面只是介绍了实现单例模式的三种基本方法。接下来,我们考虑一下如何精益求精地实现单例模式。

  • 方案一(双重检查加锁)

    该方法是对 懒汉式 的优化。在懒汉式里面我们需要每次都进行线程安全检查,每次的线程安全检查都会消耗一部分时间。在这里,我们利用两次检查实例是否为空来减少线程安全检查的次数。这样一来,整个过程中只需要几次同步(线程安全检查),从而起到了优化作用。具体实现如下:

    package 单例模式;
    
    /**
    * 懒汉式优化 ---- 双重检查加锁
    * 疑问: 这里到底需不需要 volatile 关键字
    * @author by kissx on 2017/1/30.
    */
    public class Singleton {
      private volatile static Singleton singleton;
    
      public static Singleton getInstance(){
          if (singleton == null) {
              synchronized (Singleton.class){
                  if (singleton == null) {
                      singleton = new Singleton();
                  }
              }
          }
          return singleton;
      }
    }
    

    说明:正如代码中所述,这里不知道是否必须使用 volatile 关键字。个人认为可以不使用。这样的话该方法还是不错的。如果必须加 volatile 关键字,那么会影响 jvm 对代码的优化,从而影响整个的运行速度。

  • 方案二

    这里利用的是 java 内部机制,巧妙的解决了线程安全问题以及加载完成的时间问题。这里利用是类中的静态内部类,具体这里不再描述。实现代码可以借鉴上面的举例说明。

  • 方案三(最佳)

    这里利用单元素的枚举类型来实现单例模式。枚举类型实质上相当于功能齐全的类,该处的单元素就是那个唯一的实例。实现代码如下

    package 单例模式;
    
    /**
     * 利用枚举实现单例模式,更高效、安全、简洁
     * @author  by kissx on 2017/1/30.
     */
    public enum EnumSingleton {
        uniqueInstance; //一个元素表示一个实例
    
        //操作方法
        EnumSingleton(){
    
        }
    }

总结

对于单例模式在实际应用中要学会扩展,比如对于某个类的实例对象的限制不是一个而是有限的几个,这里可以利用单例模式加上缓存技术来实现。
就这些吧,累呀~~~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值