单例模式的要点(写出一个单例模式)

目录一、单例模式的定义和应用场景(一)定义及基本要点(二)应用场景二、饿汉式单例模式(一)基本代码展示分析(二)基本分析和建议三、懒汉式单例模式(双重检查锁)(一)基本代码展示分析(二)基本分析和建议四、静态内部类实现单例模式(一)基本代码展示分析(二)基本分析和建议五、注册式单例模式(一)枚举式单例模式代码及分析:(EffectiveJa…

大家好,又见面了,我是你们的朋友全栈君。

目录

一、单例模式的定义和应用场景

(一)定义及基本要点

(二)应用场景

二、饿汉式单例模式

(一)基本代码展示分析

(二)基本分析和建议

三、懒汉式单例模式(双重检查锁)

(一)基本代码展示分析

(二)基本分析和建议

四、静态内部类实现单例模式

(一)基本代码展示分析

(二)基本分析和建议

五、注册式单例模式

(一)枚举式单例模式代码及分析:(Effective Java推荐单例模式)

(二)容器式单例模式代码及分析:(适用于实例非常多的情况,便于管理,但是是非线程安全的)

参考书籍、文献和资料


一、单例模式的定义和应用场景

(一)定义及基本要点

单例模式是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

该模式有三个基本要点:

  • 一是这个类只能有一个实例;
  • 二是它必须自行创建这个实例;
  • 三是它必须自行向整个系统提供这个实例。

(二)应用场景

应用场景:J2EE中的ServlertContext、SerletContextConfig等、Spring框架应用中的ApplicationContext、数据库连接池等。

二、饿汉式单例模式

(一)基本代码展示分析

/**
 * 描述:饿汉式单例模式
 * 优点:没有任何锁,执行效率高,用户体验比懒汉式单例模式更好
 * 缺点:类加载的时候就初始化,不管用不用都占内存空间
 * 建议:适用于单例模式较少的场景
 *      如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
 *      如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源。
 *
 * @author yanfengzhang
 * @date 2020-01-02 20:39
 */
public class HungrySingleton {
    private final static HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
}

(二)基本分析和建议

优点:没有任何锁,执行效率高,用户体验比懒汉式单例模式更好
缺点:类加载的时候就初始化,不管用不用都占内存空间
建议:

  • 适用于单例模式较少的场景
  • 如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
  • 如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源。

三、懒汉式单例模式(双重检查锁)

(一)基本代码展示分析

/**
 * 描述:懒汉式单例模式---双重检查锁
 * 相比单锁而言,双重检查锁性能上虽然有提升,但是依旧用到了synchronized关键字总归要上锁,对程序性能还是存在一定的性能影响
 * 不算最优--存在优化空间
 *
 * 建议:如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
 *      如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源。
 *
 * @author yanfengzhang
 * @date 2020-01-02 20:53
 */
public class LazyDoubleCheckSingleton {
    /**
     * volatile 关键字可以保证线程间变量的可见性,还有一个作用就是阻止局部重排序的发生
     */
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton() {
    }

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

(二)基本分析和建议

相比单锁而言,双重检查锁性能上虽然有提升,但是依旧用到了synchronized关键字总归要上锁,对程序性能还是存在一定的性能影响。注意里面volatile的使用!!!

建议:

  • 如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
  • 如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源。

四、静态内部类实现单例模式

(一)基本代码展示分析

/**
 * 描述:屏蔽饿汉式单例模式的内存浪费问题和双重检查锁中synchronized的性能问题
 * 避免因为反射破坏单例
 *
 * @author yanfengzhang
 * @date 2020-01-02 21:08
 */
public class LazyInnerClassSingleton {
    /**
     * 使用LazyInnerClassSingleton的时候会先默认初始化换内部类
     * 如果没有使用,则内部类是不加载的
     */
    private LazyInnerClassSingleton() {
        /*为了避免反射破坏单例,需要在构造方法中增加限制,一旦出现多次重复创建,直接抛出异常*/
        if (null != Lazyholder.LAZY_INNER_CLASS_SINGLETON) {
            throw new RuntimeException("创建LazyInnerClassSingleton异常,不允许创建多个实例!");
        }
    }

    /**
     * 每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不会被重写、重载
     */
    public static final LazyInnerClassSingleton getInstance() {
        /*在返回结果前,一定会先加载内部类*/
        return Lazyholder.LAZY_INNER_CLASS_SINGLETON;
    }

    /**
     * 默认不加载
     */
    private static class Lazyholder {
        private static final LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton();
    }
}

(二)基本分析和建议

屏蔽饿汉式单例模式的内存浪费问题和双重检查锁中synchronized的性能问题,同时考虑避免因为反射破坏单例问题。

相对而言性能最好!

五、注册式单例模式

注册式单例模式/登记式单例模式,将每个实例都登记到一个地方,使用唯一的标识获取单例。

注册单例模式有两种:枚举式单例模式+容器式单例模式

(一)枚举式单例模式代码及分析:(Effective Java推荐单例模式)

/**
 * 描述:注册式单例模式/登记式单例模式,将每个实例都登记到一个地方,使用唯一的标识获取单例。
 * 注册单例模式有两种:枚举式单例模式+容器式单例模式
 * 此为枚举式单例模式---Effective Java推荐单例模式
 *
 * @author yanfengzhang
 * @date 2020-01-03 09:59
 */
public enum EnumSingleton {
    /*枚举式单例模式*/
    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }
}

(二)容器式单例模式代码及分析:(适用于实例非常多的情况,便于管理,但是是非线程安全的)

/**
 * 描述:注册式单例模式/登记式单例模式,将每个实例都登记到一个地方,使用唯一的标识获取单例。
 * 注册单例模式有两种:枚举式单例模式+容器式单例模式
 * 建议:容器式单例模式适用于实例非常多的情况,便于管理,但是是非线程安全的。
 *
 * @author yanfengzhang
 * @date 2020-01-03 10:51
 */
public class ContainerSingleton {
    private ContainerSingleton() {
    }

    private static Map<String, Object> ioc = new ConcurrentHashMap<>();

    public static Object getBean(String className) {
        synchronized (ioc) {
            if (ioc.containsKey(className)) {
                return ioc.get(className);
            }
            Object obj = null;
            try {
                obj = Class.forName(className).newInstance();
                ioc.put(className, obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return obj;
        }
    }
}

 

参考书籍、文献和资料

1.《Sring 5 核心原理与30个类手写实战》,谭勇徳,中国公信出版社,2019.

2.极客时间课程《Java性能调优实战》,刘超,2019.

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/125954.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • C++ 23种设计模式(6)-适配器模式

    C++ 23种设计模式(6)-适配器模式适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。它包括类适配器和对象适配器,本文针对的是对象适配器。举个例子,在STL中就用到了适配器模式。STL实现了一种数据结构,称为双端队列(deque),支持前后两段的插入与删除。STL实现栈和队列时,没有从头开始定义它们,而是直接使用双端队列实现的。这里双端队列就扮演了适配器的角色。队列用到了它的后端插入,前端删除。而栈用到了它的后端插入,后端删除。假设栈和队列都是一种顺序容器,有两种操作:压入和弹出。

    2022年7月25日
  • python中copy.deepcopy_clone和copy的区别

    python中copy.deepcopy_clone和copy的区别同样是copy,二者有什么不同呢今天我们就一探究竟!!!python——赋值与深浅拷贝  初学编程的小伙伴都会对于深浅拷贝的用法有些疑问,今天我们就结合python变量存储的特性从内存的角度来谈一谈赋值和深浅拷贝~~~预备知识一——python的变量及其存储  在详细的了解python中赋值、copy和deepcopy之前,我们还是要花一点时间来了解一下p…

    2022年10月2日
  • Python的包管理工具Pip

    Python的包管理工具Pip

    2021年12月10日
  • bm3d算法matlab,BM3D算法实现图像降噪.doc[通俗易懂]

    bm3d算法matlab,BM3D算法实现图像降噪.doc[通俗易懂]BM3D算法实现图像降噪广东工业大学《数字图像、数字信号处理及应用》实验报告题目图像处理综合实验院、系(部)自动化学院专业及班级学号姓名日期目录《数字图像、数字信号处理及应用》0实验报告02实验要求43实验设备44实验原理44.1利用拉普拉斯算子实现图像锐化44.2利用分段线性函数实现对比度扩展54.3余弦变换(D…

    2022年6月2日
  • layui–select使用以及下拉框实现键盘选择[通俗易懂]

    layui–select使用以及下拉框实现键盘选择[通俗易懂]layui–select使用以及下拉框实现键盘选择

    2022年4月20日
  • docker镜像导入导出_导出docker镜像

    docker镜像导入导出_导出docker镜像简介当服务器无法访问公网的时候,又要pull镜像,这个时候可以将其他服务上的镜像导出,然后再导入到要用的服务器。方法一:1.保存save-加载load格式:dockersaveIMAGE(镜像)使用dockerimages查看本机已有的镜像(也可以使用dockercommit命令把一个正在运行的容器保存为镜像),如:导出:格式:dockersaveimagesID>/存放位置/打包文件名.tardockersave42cdba9f1b08

    2022年9月5日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号