设计模式(四)单例模式

Singleton Pattern

why

问题情境:

有时候,我们在程序里面需要维持某个对象只有一个,比如常用的配置文件对象,全局使用一个就可以,如果多了就会占用内存,也不好管理,容易出错。这时候我们可以考虑一下单例模式。

一句话总结:我们希望在系统中只存在一个特定的对象而不是有多份

优点、缺点分析:

  • 时间和空间

    懒汉式单例模式:时间换空间。就是比较懒,用的时候再实例化对象。

    饿汉式单例模式:空间换时间。就是比较饥渴,在类被加载的时候就创建好了。

  • 线程安全

    在创建、修改、删除某个静态对象的时候就会出现线程的并发问题了。

    懒汉式:因为在懒汉式里面一般会有个判断,所以在多线程并发的时候就可能造成对象被多次实例化的问题,所以在懒汉式我们应该使用同步的方法对线程安全进行控制。

    饿汉式:因为在一开始就创建好对象了,只等你来取了,而虚拟机只会加载一次,所以一般不会有线程安全的问题。

    下面是双重检查加锁控制懒汉式线程安全问题:

 public class Singleton {
 
     /**
      * 此处的volatile不可以省略,否则会导致线程无序写入导致的失败情况
      * jdk1.4之前不能使用双重检查加锁,因为不同的编译器对volatile的处理结果不一致,会导致失败
      */
     private static volatile Singleton instance = null;
 
     private Singleton() {
     }
 
     public static Singleton getInstance() {
         //先检查实例是否存在,不存在才进入下面的同步块
         if (instance == null) {
             //同步块,线程安全的创建实例
             synchronized (Singleton.class) {
                 //再次检查实例是否存在,如果不存在,才真正的创建实例
                 if (instance == null) {
                     instance = new Singleton();
                 }
             }
         }
         return instance;
     }
 }

双重检查加锁,避免了在方法上使用syncronized关键词,避免了每次调用该方法时的加锁情况,减少了锁的开销

懒汉的延迟加载主要是线程安全方面会出现问题,那么也可以使用 Java的一些特性来实现线程安全方面的保证,下面给出了一个实现:

 public class InnerClassSingleton {
     /**
      * 类级的内部类,只有在第一次被使用的时候才会被装载
      */
     private static class SingletonHolder{
         /**
          * 静态初始化器,由jvm来保证线程安全
          */
         private static InnerClassSingleton instance = new InnerClassSingleton();
     }
 
     /**
      * 私有化构造方法
      */
     private InnerClassSingleton(){
     }
 
     public static InnerClassSingleton getInstance(){
         return SingletonHolder.instance;
     }
 }
  • 单例和枚举

    有关枚举的几个观点:

    1. Java的枚举类型实际上是功能齐全的类,可以有自己的属性和方法
    2. Java枚举类的基本思想是通过共有的final域为每个枚举常量导出实例的类
    3. 从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举

    根据这几个观点,我们可以用枚举来实现单例模式:

    /**
     * 使用枚举类来实现一个单例
     */
    public enum  EnumSingleton {
    
        /**
         * 定义了一个枚举的元素,它就代表了EnumSingleton的一个实例
         */
        uniqueInstance;
    
        /**
         * 这样就定义了一个单例实例的方法
         */
        public void singletonOperation(){
    
        }
    }
    
  • 缓存和单例

    我们还可以通过单例来实现缓存:

public class SingletonCache {
 
     /**
      * 缓存数据的容器
      */
     private Map<String, Object> map = Maps.newHashMap();
 
     public Object getValue(String key) {
         //从缓存里面取值
         Object obj = map.get(key);
       if (obj == null) {
             //如果没有,就到数据库或者文件中去读取数据放到缓存中
             obj = key + ", value";
             //把获取的值设置到map中去
             map.put(key, obj);
       }
         return obj;
     }

     public Object getValueJava8(String key) {
         //同上,但是使用的事java8函数表达式
         return map.computeIfAbsent(key, k -> k + ", value");
     }
 }

既然缓存是通过单例模式来实现的,那么反过来讲,我们就可以使用缓存来实现单例模式了:

  /**
   * 使用缓存来实现单例(没有考虑同步和缓存的清除)根据上面的同步知识可以自己加上
   */
  public class CacheSingleton {
      /**
       * 定义一个默认的key,标识缓存中的单例
       */
      private final static String DEFAULT_KEY= "one";

      /**
       * 缓存实例的容器
       */
      private static Map<String, CacheSingleton> map = Maps.newHashMap();

      private CacheSingleton(){
      }

      public CacheSingleton getInstance() {
          //获取实例
          CacheSingleton singleton = map.get(DEFAULT_KEY);
          if (singleton == null) {
              singleton = new CacheSingleton();
              map.put(DEFAULT_KEY, singleton);
          }
          return singleton;
      }

  }

when

当需要控制一个类的实例只有一个,并且客户端只能从一个全局访问点访问它时,我们可以使用单例模式

相关模式:

单例模式在其他模式中使用比较多,比如:抽象工厂方法中的具体工厂类

how

单例模式

code

已在上面给出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值