设计模式:创建型单例模式

Posted JavaLog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式:创建型单例模式相关的知识,希望对你有一定的参考价值。

因为最近本人也在学习设计模式,就在此记录一下学习过程算是分享一下吧

设计模式
设计模式:创建型(一)单例模式

设计模式在维基百科中是这样定义的,意指软件设计中普遍存在问题的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的。

概念

设计模式:创建型(一)单例模式

单例模式属于创建型模式的一种,单例对象的类保证只有一个实例存在。

在我们的系统中,许多时候只需要一个全局的对象实例,这样有利用不重复创建对象,协调系统整体的行为。

比如,在某个服务器程序中,该服务器的配置文件只保存一份,这些保存的数据有一个全局的单例对象读取,同样,也简化了在复杂环境下的配置管理。

设计模式:创建型(一)单例模式


为什么要使用单例


设计模式:创建型(一)单例模式

全局日志管理,以前的系统中创建全局日志对象,现在日志对象使用单例的就可以解决日志冲突等问题

全局配置管理,当前服务器配置文件使用一个全局的单例对象读取

系统中全局的id生成器,所有生成id的地方全使用这个单例类,保证id唯一


怎么创建一个单例模式


设计模式:创建型(一)单例模式

创建单例模式,我们的关注点无非其实就是下面几个

构造函数是私有的,防止外部通过new 创建

考虑创建对象时的线程安全问题

考虑是否支持延迟加载

考虑getInstance()性能是否高


一、饿汉式(全局的单例实例在累加载时创建)

public class HungryIdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final HungryIdGenerator instance = new HungryIdGenerator();
private HungryIdGenerator() { }
public static HungryIdGenerator getInstance(){ return instance; }
public Long getId(){ return id.incrementAndGet(); }}
@SpringBootTestpublic class SingleApplicationTests {
@Test public void HungryIdGeneratorTest() { HungryIdGenerator instance = HungryIdGenerator.getInstance();    System.out.println(instance.getId());// 输出1 HungryIdGenerator i1 = HungryIdGenerator.getInstance(); System.out.println(i1.getId()); // 输出2  }}

分析: 饿汉式单例是比较简单的,在类加载时,instance实例就已经创建初始化完成,所以instance实例的创建过程是线程安全的。这种方式不支持懒加载,即使用到时在加载


二、懒汉式单例


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

@Test public void LazyIdGeneratorTest() { LazyIdGenerator instance = LazyIdGenerator.getInstance();    System.out.println(instance.getId());// 输出1 LazyIdGenerator i1 = LazyIdGenerator.getInstance(); System.out.println(i1.getId()); // 输出2 }

分析:懒汉式也就是字面意思,比较懒,什么时候用到什么时候加载。因为不加锁并发场景下可能会出现获取到两个实例到情况,所以我们加了一把大锁,所以并发度就大大降低了 。如果这个单例偶尔用到还可以接受,如果频繁用到,那频繁的加锁,解锁并发度就大大的降低了,造成性能瓶颈


三、双重检测单例

public class DoubleCheckIdGenerator { private AtomicLong id = new AtomicLong(0);
private static DoubleCheckIdGenerator instance ;
private DoubleCheckIdGenerator() { }
public static DoubleCheckIdGenerator getInstance() { if (instance == null) { synchronized(DoubleCheckIdGenerator.class) { if (instance == null) { instance = new DoubleCheckIdGenerator(); } } } return instance; } public long getId() { return id.incrementAndGet(); }}
@Test public void doubleCheckIdGeneratorTest() { DoubleCheckIdGenerator instance = DoubleCheckIdGenerator.getInstance(); System.out.println(instance.getId()); DoubleCheckIdGenerator i1 = DoubleCheckIdGenerator.getInstance(); System.out.println(i1.getId()); }

分析:饿汉式不支持延迟加载,懒汉式有并发性能问题,双重检查支持拦截在和支持高并发的单例,只要是创建了实例,就不会在进入加锁的逻辑。针对指令重排序问题,造成实例被new出新的实例问题在新版本中不会重现已经被jdk优化,只有低版本才出现因为重排造成的非单例对象创建


四、静态内部类


public class InnerIdGenerator { private AtomicLong id = new AtomicLong(0);
private InnerIdGenerator() { }
private static class InnerIdGeneratorHolder{ private static final InnerIdGenerator instance = new InnerIdGenerator(); }
public static InnerIdGenerator getInstance(){ return InnerIdGeneratorHolder.instance; }
public Long getId(){ return id.incrementAndGet(); }}
@Test public void innerIdGeneratorTest() { InnerIdGenerator instance = InnerIdGenerator.getInstance(); System.out.println(instance.getId()); InnerIdGenerator i1 = InnerIdGenerator.getInstance(); System.out.println(i1.getId()); }

分析:使用java的静态内部类创建单例,当外部类加载时不会创建,只有当调用getInstance方法时InnerIdGeneratorHolder才会触发加载,instance的唯一性,创建过程的安全性都有jvm保证,所以这种方式既实现类延迟加载又实现了线程安全


五、枚举单例模式

public enum IdGeneratorEnum { /** * 单例对象 */ INSTANCE;
private AtomicLong id = new AtomicLong(0);
public Long getId(){ return id.incrementAndGet(); }}
@Test public void enumIdGeneratorTest() { IdGeneratorEnum instance = IdGeneratorEnum.INSTANCE; System.out.println(instance.getId()); IdGeneratorEnum i1 = IdGeneratorEnum.INSTANCE; System.out.println(i1.getId()); }

分析:利用java枚举类自身的特点来实现单例,最简单实现单例的一种方式,保证了线程安全和实例的唯一


饿汉PK懒汉
设计模式:创建型(一)单例模式
设计模式:创建型(一)单例模式

饿汉式:类加载时就已经把instance创建好,所以实例的创建是线程安全的,但是不支持延迟加载

懒汉式:相对于饿汉式是支持懒加载,这种会导致频繁的加锁,解锁,导致并发度低,造成性能瓶颈

结论


设计模式:创建型(一)单例模式

个人推荐如果必须使用单例,就使用枚举,最简单,最安全


源码在github   

https://github.com/TianPuJun/tz_design_pattern.git
设计模式:创建型(一)单例模式




扫码关注我们
个人微信:c1041067258
学习Java 轻松入门





以上是关于设计模式:创建型单例模式的主要内容,如果未能解决你的问题,请参考以下文章

基于多线程任务队列执行时间测试——泛型单例模式落地

技能篇:实际开发常用设计模式

设计模式总览

设计模式

《C#零基础入门之百识百例》(八十二)泛型类型参数Where约束 -- 泛型单例

《C#零基础入门之百识百例》(八十二) 泛型类型参数Where约束 -- 泛型单例