设计模式:单例模式多种实现及应用场景Java版

开篇

设计模式对于很多小伙伴来说都是它认识你,但是你不认识它,设计模式可以帮助我们简化代码,提高代码的复用率,减少代码的耦合性,以及增加代码的重复利用性,但是设计模式并非是好用的代言,有些时候也会给我们代来很多问题,比如简单的判断语句会变成复杂的多类关联,也会引发一些安全问题,比如今天要说的单例模式。

基本介绍

单例模式(Singleton Pattern)是Java中最简单的设计模式之一(暗藏玄机)。属于创建模式之一,提供了一种创建对象的最佳方式。一个单一的类,负责创建自己的对象,同时保证只有单个对象被创建,对外提供唯一的方法来获取该类的对象,并且不需要实例化对象。因此可以总结出以下几点:

  1. 只能有一个实例(注意防止并发)。
  2. 实例必须是自己创建的。
  3. 必须给其他对象这一实例。

主要解决

一个被全局使用的类,被频繁的创建,当你想控制资源,节省资源时,你就要使用单例模式。

关键代码

  1. 默认构造函数要私有。
  2. 提供对外获取对象的唯一方法。

使用场景

  1. Spring中Bean容器管理的对象,默认下就是通过单例构建对象
  2. 系统中ID生成器,通常采用单例模式创建出对象
  3. 比如定时任务中,发现任务和触发执行器执行的类,一般都设计成单例模式获取(频繁使用)

缺点

  1. 没有接口,不能被继承 
  2. 控制不好会有并发问题,除了枚举类之外,可以被反射获取新的实例
  3. 单一原则下,类应该只关心内部,不应该关心外部的创建,违背单一原则

具体实现并带有优缺点分析

1、饿汉式

/**
 * 饿汉式
 * 利用classload上来初始化实例,解决并发问题
 * 但是此方法比较大的问题就是,当初始化单利比较耗时,或者很久不会使用的时候,浪费内存
 * 是最长用方案
 */
public class SingleObject {
    //单例模式必然是上来就私有化
    private SingleObject() {
    }
    //创建一个唯一的类对象
    private static SingleObject instance = new SingleObject();
    //提供一个获取的方法
    public static SingleObject getInstance() {
        return instance;
    }
}

2、懒汉式,不考虑并发

/**
 * 因此引出懒汉式,节省内存
 * 但是此情况又会出现线程并发问题
 * 在判断 instance == null 的时候可能出现并发,多线程同时到达new
 */
class SingleObjectLazy {
    //依然是上来私有构造
    private SingleObjectLazy() {
    }

    //创建静态对象变量,不初始化
    private static SingleObjectLazy instance;

    //使用时在初始化
    public static SingleObjectLazy getInstance() {
        if (instance == null) {
            instance = new SingleObjectLazy();
        }
        return instance;
    }
}

3、懒汉式,加锁

/**
 * 由于并发引出加锁,
 * 但是很难出现同时获取instance而且还是为空的情况
 * 一旦创建后,不在需要锁住
 * 此时就会造成效率低下
 */
class SingleObjectSyncLazy {

    private SingleObjectSyncLazy() {
    }

    private static SingleObjectSyncLazy instance;

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

4、懒汉式,双重检查代替方法锁,提高效率,防止指令重排加入volatile

/**
 * 使用双重检查机制
 * 只有当并发null的时候才需要进入加锁,此时并发的概率很小
 * 还是有问题的,不加上 volatile修饰的话,会出指令重排
 * 同时 synchronized  只保单线程的结果正确,不会保证指令重排,
 */
class SingleObjectSyncLazyDouble {
    private SingleObjectSyncLazyDouble() {
    }

    //防止指令重排
    private volatile static SingleObjectSyncLazyDouble instance;

    public static SingleObjectSyncLazyDouble getInstance() {
        if (instance == null) { //只有并发null的时候进入加锁
            synchronized (SingleObjectSyncLazyDouble.class) {
                //as-if-serial synchronized 只会保证单线程内执行操作结果,指令依然会重排
                if (instance == null) { //不加,就会直接创建两个
                    /*此处会出现指令重排
                     *  instance = new SingleObjectSyncLazyDouble();
                     *  1、开辟内存
                     *  2、实例化
                     *  3、引用指向
                     *  不加volatile 就会出现混乱效果,导致先引用,这时候第二线程刚好到达第一处检查,
                     *  直接走人了。就出现了大问题
                     * */
                    instance = new SingleObjectSyncLazyDouble();
                }
            }
        }
        return instance;
    }
}

5、懒加载、静态内部类实现。

/**
 * 静态内部类的使用,静态内部类内部的方法是在第一次调用的时候才会加载,因此可以避免一上来就被初始化的问题
 * 也是一种懒加载的形式
 */
class SingleObjectStaticLazy {
    private SingleObjectStaticLazy() {
    }

    private static class StaticClass {
        private static SingleObjectStaticLazy instance = new SingleObjectStaticLazy();
    }

    public static SingleObjectStaticLazy getInstance() {
        return StaticClass.instance;
    }
}

6、枚举

以上都可以通过反射获取新的实例,枚举类被反射会直接触发异常

该方法在1.5才开始引入,不受众,不过是小黄书推荐的方式。

/**
 * 枚举
 */
public enum SingleObject {
    INSTANCE;
}

结束

以上就是单例模式创建的方式,优点缺点,希望可以对大家有所帮助 。

你可能感兴趣的:(要优雅--设计模式,设计模式,java,经验分享,程序人生)