创建型【单例模式】

设计模式主要分为三大类,创建型,结构型和行为型。
创建型设计模式主要解决的是对象创建的问题。
单例模式(Singleton Design Pattern)是最常见的创建型设计模式。

单例模式是什么?

一个类只能创建一个对象(也可以称为实例),这个类就是单例类,这种设计模式就是单例设计模式。

为什么需要单例模式?

需要使用单例模式或者使用单例模式更好的场景有很多,以下简单举两个场景的例子。

解决资源冲突问题

比如我们有一个诉求,我们需要系统把所有的日志都打印到一个文件中。这时候我们需要实现一个日志类。多线程的环境下,我们就会创建多个日志类,多个日志类实例并发往文件中打印文件的时候,有可能会出现彼此覆盖的情况。这时候把日志类设计成一个单例,就可以解决这个问题。

某些类从业务属性来说,就应该是单例

比如读取系统的配置文件,读到内存中,构建一个属性类。大多数系统只能有一套配置文件,因为配置类也应该只有一个实例。而且创建多个对象还会造成资源的浪费。

怎样实现单例模式

实现单例模式要考虑以下几个问题:

  1. 单例类的构造函数必须是private,这样才能避免外部通过new创建实例
  2. 考虑创建对象的线程安全问题
  3. 是否支持延迟加载
  4. 考虑getInstance()性能是否高(是否加锁)。
    目前主要有5种方案来实现单例模式

饿汉模式

饿汉模式不支持延迟加载。在类加载的时候,instance实例就已经创建好了。
个人比较推崇饿汉模式。实现简单。而且能提早暴露问题。

public class IdGeneratorEager {

    private AtomicLong id = new AtomicLong(0);
    private static final IdGeneratorEager instance = new IdGeneratorEager();

    private IdGeneratorEager() {}

    public static IdGeneratorEager getInstance() {
        return instance;
    }

    public Long getId() {
        return id.getAndAdd(1);
    }

}

懒汉式

懒汉式支持延迟加载,但是需要给getInstance加一把大锁。在多并发的环境下,非常影响并发度,影响系统性能。

public class IdGeneratorLazy {
    private final  AtomicLong      id = new AtomicLong(0);
    private static IdGeneratorLazy instance;

    private IdGeneratorLazy() {}

    public static synchronized IdGeneratorLazy getInstance() {
        if (instance == null) {
            instance = new IdGeneratorLazy();
        }
        return instance;
    }

    public Long getId() {
        return id.getAndAdd(1);
    }

}

双重检测

基于懒汉式做的改进。

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

    private static volatile IdGeneratorDoubleCheck instance;

    private IdGeneratorDoubleCheck(){}

    public static IdGeneratorDoubleCheck getInstance() {
        if (instance == null) {
            synchronized (IdGeneratorDoubleCheck.class) {
                if (instance == null) {
                    instance = new IdGeneratorDoubleCheck();
                }
            }
        }
        return instance;
    }

    public Long getId() {
        return id.getAndAdd(1);
    }
    
}

静态内部类

比双重检测的实现简单一点。而且支持延迟加载

public class IdGeneratorStaticNested {
    private AtomicLong atomicLong = new AtomicLong(0);

    private IdGeneratorStaticNested() {}

    private static class SingletonHolder {
        static IdGeneratorStaticNested instance = new IdGeneratorStaticNested();
    }

    public static IdGeneratorStaticNested getInstance() {
        return SingletonHolder.instance;
    }

    public Long getId() {
        return atomicLong.getAndAdd(1);
    }
}

单例枚举实现

只能有一个枚举值。最简单的方式。

public enum IdGeneratorEnumImpl {
    INSTANCE;
    private AtomicLong atomicInteger = new AtomicLong(0);

    public Long getId() {
        return atomicInteger.getAndAdd(1);
    }
}

以上代码均进行过验证

public class SingletonPattern {
    public static void main(String[] args) {
        //饿汉模式
        IdGeneratorEager idGen1 = IdGeneratorEager.getInstance();
        System.out.println(idGen1.getId());

        //懒汉模式
        IdGeneratorLazy idGen2 = IdGeneratorLazy.getInstance();
        System.out.println(idGen2.getId());

        //双重检测
        IdGeneratorDoubleCheck idGen3 = IdGeneratorDoubleCheck.getInstance();
        System.out.println(idGen3.getId());

        //静态内部类实现
        IdGeneratorStaticNested idGen4 = IdGeneratorStaticNested.getInstance();
        System.out.println(idGen4.getId());

        //枚举实现
        System.out.println(IdGeneratorEnumImpl.INSTANCE.getId());
    }
}

在这里插入图片描述

单例模式的问题

  1. 单例对OOP特性的支持不友好
  2. 单例会隐藏类之间的依赖关系
  3. 单例对代码的扩展性不友好
  4. 单例对代码的可测试性不友好
  5. 单例不支持有参数的构造函数

单例模式扩展

怎样理解单例模式的唯一性

单例模式的唯一性是进程级别的唯一。

怎样实现线程级别的唯一性

ThreadLocal实现方案,创建ThreadLocal实例对象,并且重写initialValue方法

public class IdGeneratorThreadLocal {
    private static final ThreadLocal<IdGeneratorThreadLocal> threadLocal = ThreadLocal.withInitial(IdGeneratorThreadLocal::new);
    private IdGeneratorThreadLocal() {}

    public static IdGeneratorThreadLocal getInstance() {
        return threadLocal.get();
    }

}

HashMap实现方案

public class IdGeneratorThreadLevel {
    private AtomicLong atomicLong = new AtomicLong(0);
    private static final Map<Long, IdGeneratorThreadLevel> map = new HashMap<>();

    private IdGeneratorThreadLevel() {}

    public static IdGeneratorThreadLevel getInstance() {
        Long threadId = Thread.currentThread().getId();
        map.putIfAbsent(threadId, new IdGeneratorThreadLevel());
        return map.get(threadId);
    }

    public Long getId() {
        return atomicLong.getAndAdd(1);
    }
}

多例模式怎样实现

public class IdGeneratorMultiInstance {
    private              AtomicLong                                           id    = new AtomicLong(0);
    public static final  ConcurrentHashMap<Integer, IdGeneratorMultiInstance> map   = new ConcurrentHashMap<>();
    private static final int                                                  count = 3;

    private IdGeneratorMultiInstance() {}

    static {
        for (int i = 0; i < count; i++) {
            map.put(i, new IdGeneratorMultiInstance());
        }
    }

    public IdGeneratorMultiInstance getInstance(int id) {
        return map.get(id);
    }

    public IdGeneratorMultiInstance randomGetInstance() {
        Random random = new Random();
        int id = random.nextInt(count) + 1;
        return map.get(id);
    }

    public Long getId(){
        return id.getAndAdd(1);
    }
}

集群级别的单例

通过分布式锁来实现。每次获取id从共享存储中读。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值