小言_互联网的博客

单例模式的使用总结

355人阅读  评论(0)

目录

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

(一)定义及基本要点

(二)应用场景

二、饿汉式单例模式

(一)基本代码展示分析

(二)基本分析和建议

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

(一)基本代码展示分析

(二)基本分析和建议

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

(一)基本代码展示分析

(二)基本分析和建议

五、注册式单例模式

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

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

参考书籍、文献和资料


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

(一)定义及基本要点

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

该模式有三个基本要点

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

(二)应用场景

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

二、饿汉式单例模式

(一)基本代码展示分析


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

(二)基本分析和建议

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

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

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

(一)基本代码展示分析


  
  1. /**
  2. * 描述:懒汉式单例模式---双重检查锁
  3. * 相比单锁而言,双重检查锁性能上虽然有提升,但是依旧用到了synchronized关键字总归要上锁,对程序性能还是存在一定的性能影响
  4. * 不算最优--存在优化空间
  5. *
  6. * 建议:如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
  7. * 如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源。
  8. *
  9. * @author yanfengzhang
  10. * @date 2020-01-02 20:53
  11. */
  12. public class LazyDoubleCheckSingleton {
  13. /**
  14. * volatile 关键字可以保证线程间变量的可见性,还有一个作用就是阻止局部重排序的发生
  15. */
  16. private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
  17. private LazyDoubleCheckSingleton() {
  18. }
  19. public static LazyDoubleCheckSingleton getInstance() {
  20. if ( null == lazyDoubleCheckSingleton) {
  21. synchronized (LazyDoubleCheckSingleton.class) {
  22. if ( null == lazyDoubleCheckSingleton) {
  23. lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
  24. }
  25. }
  26. }
  27. return lazyDoubleCheckSingleton;
  28. }
  29. }

(二)基本分析和建议

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

建议:

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

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

(一)基本代码展示分析


  
  1. /**
  2. * 描述:屏蔽饿汉式单例模式的内存浪费问题和双重检查锁中synchronized的性能问题
  3. * 避免因为反射破坏单例
  4. *
  5. * @author yanfengzhang
  6. * @date 2020-01-02 21:08
  7. */
  8. public class LazyInnerClassSingleton {
  9. /**
  10. * 使用LazyInnerClassSingleton的时候会先默认初始化换内部类
  11. * 如果没有使用,则内部类是不加载的
  12. */
  13. private LazyInnerClassSingleton() {
  14. /*为了避免反射破坏单例,需要在构造方法中增加限制,一旦出现多次重复创建,直接抛出异常*/
  15. if ( null != Lazyholder.LAZY_INNER_CLASS_SINGLETON) {
  16. throw new RuntimeException( "创建LazyInnerClassSingleton异常,不允许创建多个实例!");
  17. }
  18. }
  19. /**
  20. * 每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不会被重写、重载
  21. */
  22. public static final LazyInnerClassSingleton getInstance() {
  23. /*在返回结果前,一定会先加载内部类*/
  24. return Lazyholder.LAZY_INNER_CLASS_SINGLETON;
  25. }
  26. /**
  27. * 默认不加载
  28. */
  29. private static class Lazyholder {
  30. private static final LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton();
  31. }
  32. }

(二)基本分析和建议

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

相对而言性能最好!

五、注册式单例模式

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

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

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


  
  1. /**
  2. * 描述:注册式单例模式/登记式单例模式,将每个实例都登记到一个地方,使用唯一的标识获取单例。
  3. * 注册单例模式有两种:枚举式单例模式+容器式单例模式
  4. * 此为枚举式单例模式---Effective Java推荐单例模式
  5. *
  6. * @author yanfengzhang
  7. * @date 2020-01-03 09:59
  8. */
  9. public enum EnumSingleton {
  10. /*枚举式单例模式*/
  11. INSTANCE;
  12. private Object data;
  13. public Object getData() {
  14. return data;
  15. }
  16. public void setData(Object data) {
  17. this.data = data;
  18. }
  19. public static EnumSingleton getInstance() {
  20. return INSTANCE;
  21. }
  22. }

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


  
  1. /**
  2. * 描述:注册式单例模式/登记式单例模式,将每个实例都登记到一个地方,使用唯一的标识获取单例。
  3. * 注册单例模式有两种:枚举式单例模式+容器式单例模式
  4. * 建议:容器式单例模式适用于实例非常多的情况,便于管理,但是是非线程安全的。
  5. *
  6. * @author yanfengzhang
  7. * @date 2020-01-03 10:51
  8. */
  9. public class ContainerSingleton {
  10. private ContainerSingleton() {
  11. }
  12. private static Map<String, Object> ioc = new ConcurrentHashMap<>();
  13. public static Object getBean(String className) {
  14. synchronized (ioc) {
  15. if (ioc.containsKey(className)) {
  16. return ioc.get(className);
  17. }
  18. Object obj = null;
  19. try {
  20. obj = Class.forName(className).newInstance();
  21. ioc.put(className, obj);
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. }
  25. return obj;
  26. }
  27. }
  28. }

 

参考书籍、文献和资料

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

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


转载:https://blog.csdn.net/xiaofeng10330111/article/details/105652399
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场