TOP

设计模式 --单例模式(一)
2020-07-28 06:08:16 】 浏览:30次 本网站的内容取自网络,仅供学习参考之用,绝无侵犯任何人知识产权之意。如有侵犯请您及时与本人取得联系,万分感谢。
Tags:设计模式 单例 模式

前言

单例模式应该是我们最熟悉的模式了,如果说要随便抓一个程序员,让他说一说最熟悉的集中设计模式,我想肯定有单例模式。

我们这节就全面的来讲解一下单例模式。

为什么要用单例模式

单例模式理解起来非常简单。在一个系统中,一个类只允许创建一个对象,那这个类就是单例类,这种设计模式就叫做单例设计模式。

为什么需要单例模式呢?首先我们得熟知他的运用场景。就是某个类比如工厂类,配置类,我们系统中只需要一份得,无需多次重复创建的类,我们就可以用单例模式。

单例模式的实现方式

饿汉式

饿汉式的实现方式比较简单。在类加载的时候,instance静态实例就已经创建并初始化好了,所以,instance实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载(在真正使用到的时候,再创建),代码如下:

public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static final IdGenerator instance = new IdGenerator();
  private IdGenerator() {}
  public static IdGenerator getInstance() {
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

IdGenerator类是被静态变量修饰并且是直接实例化的。所以当我们调用getInstance()方法,jvm会加载IdGenerator,(加载的过程中,jvm会经历加载->验证->准备->解析->初始化,该过程是天然加锁的),初始化完成以后成员变量instance就指向了IdGenerator实例对象。又因为IdGenerator的构造方法是private修饰的。所以通过该方法我们就实现了饿汉式的单例模式。

懒汉式

有饿汉式,就有对应的懒汉式。懒汉式相对于饿汉式的优势是支持延迟加载。具体代码如下:

public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private IdGenerator() {}
  public static synchronized IdGenerator getInstance() {
    if (instance == null) {
      instance = new IdGenerator();
    }
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

不过懒汉式的缺点也很明显,我们给getInstance()方法加了一把大锁,导致这个函数的并发度很低。量化一下的话,并发度是1,也就相当于串行操作了。而这个函数实在单例使用期间,一直会被调用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,如果频繁地使用到,那频繁的加锁和释放锁以及并发度低等问题,会导致性能很差。

双重检查锁

饿汉式不支持延迟加载,懒汉式有性能问题。那么有没有一种及支持延迟加载又支持高并发的单例实现方式呢?

有的,双重检查锁登场~我们来看如下代码:

public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static volatile IdGenerator instance;
  private IdGenerator() {}
  public static IdGenerator getInstance() {
    if (instance == null) {
      synchronized(IdGenerator.class) { // 此处为类级别的锁
        if (instance == null) {
          instance = new IdGenerator();
        }
      }
    }
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

这种实现方式中,只要instance被创建之后,即使再调用geiInstance()函数也不会再进入加锁逻辑中。所以,这种方式实现解决了懒汉式并发度低的问题。

注意,我们给instance成员变量加上了volatile的关键字。为什么呢?

重排序可能会让该代码出现问题,因为我们new一个对象的顺序是 开辟内存空间->创建对象->指向该内存空间。如果我们不加上volatile关键字,就可能会发生重排序,变成 开辟内存空间->指向该内存空间->创建对象。

大家想一想,是不是就有可能导致IdGenerator对象被new出来,并且赋值给instance之后,并没有来得及初始化,就被另一个线程使用了。

静态内部类

我们再来看一种比双重检查锁更加简单的实现方法,那就是利用java的静态内部类。它有点类似饿汉式,但又能做到延迟加载。我们来看看它的实现:


public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private IdGenerator() {}

  private static class SingletonHolder{
    private static final IdGenerator instance = new IdGenerator();
  }
  
  public static IdGenerator getInstance() {
    return SingletonHolder.instance;
  }
 
  public long getId() { 
    return id.incrementAndGet();
  }
}

SingletonHolder是一个静态内部类,当外部类IdGenerator被加载的时候,并不会创建SingletonHolder实例对象。只有当调用getInstance()方法的时候,SingletonHolder才会被加载,这个时候才会创建instance。instance的唯一性、创建过程的线程安全性,都由JVM来保证,所以,这种实现方式既保证了线程安全,又能做到延迟加载。

枚举

我们再来看最后一种实现方式,枚举。

这种方式通过java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。具体代码如下:

public enum IdGenerator {
  INSTANCE;
  private AtomicLong id = new AtomicLong(0);
 
  public long getId() { 
    return id.incrementAndGet();
  }
}

总结

单例模式是设计模式中比较简单一种,也是每个程序员必须掌握的设计模式。五种实现方式都应该掌握。

我们最后再讨论一个问题,延迟加载的好坏?

其实就我而言,我反而觉得饿汉式是最简单,也是相对最优的实现方式。为什么呢?其实大家好像有一个共识,提前初始化是一种浪费资源的行为。最好的方式应该是再用到的时候再去初始化。但是仔细想一想真的是这样的吗?

我们大部

请关注公众号获取更多资料


设计模式 --单例模式(一) https://www.cppentry.com/bencandy.php?fid=97&id=296181

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇springboot flowable 整合框架项.. 下一篇设计模式(12) 代理模式

评论

验 证 码:
表  情:
内  容: