当前位置: 首页 > news >正文

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

一、什么是单例模式

        单例模式,属于创建类型的一种常用的设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)。

        对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。(摘自百度百科)

        单例模式的应用场景有很多:

        1、数据库连接池的设计与实现;

        2、多线程的线程池设计与实现;

        3、Spring中创建的Bean实例默认都是单例;

        4、Java-Web中,一个Servlet类只有一个实例……

二、单例模式实现

2.1、饿汉模式

public class SingularDemo3 {private static SingularDemo3 instance = new SingularDemo3();private SingularDemo3() {System.out.println("SingularDemo3");}public static SingularDemo3 getInstance(){// 在程序启动的时候直接运行加载,后续有外部需要使用的时候获取即可// 导致的问题就像你下载个游戏软件,可能你游戏地图还没有打开呢,但是程序已经将这些地图全部实例化return instance;}
}

可以看到上述代码,很简单,直接在启动时加载,但是导致的就是,很多没用到的东西,他也给加载了,造成不必要的负担。

2.2、懒汉模式

2.2.1、使用synchronized锁

public class SingularDemo2 {public static volatile SingularDemo2 instance;private SingularDemo2() {  // 设置成私有,不允许外面调用System.out.println("SingularDemo2");}public static synchronized SingularDemo2 getInstance(){if(instance != null)    return instance;return new SingularDemo2();}
}

这种方式,是不用不加载,用到的时候再加载,使用synchronized加锁,保证只有一个线程访问,这种是线程安全的。但锁的粒度太大,导致竞争激烈,造成不必要的资源浪费。

同时这里的volatile,是防止指令重排序,导致导致对象未初始化完全。

2.2.2、使用双重校验锁

public class SingularDemo5 {public static volatile SingularDemo5 instance; // 要用volatile修饰,防止指令重排序private SingularDemo5() {System.out.println("singularDemo5");}public static SingularDemo5 getInstance(){if(instance != null)    return instance; // 如果有,直接返回synchronized (SingularDemo5.class){ // 锁住此对象if(instance != null)    return instance;  // 进入之后再判断一次instance = new SingularDemo5();return instance;}}
}

双重校验锁,降低了锁的粒度,只有当instance没有被初始化的时候,第一个访问的线程才回去实例化它。这里内部还要有一个判断instance是否为null,是为了防止同时多个线程竞争,一个线程初始化instance之后,其他线程也竞争到锁,再去初始化instance,所以进入临界区之后第一件事就是看看是否已经初始化好了。

这里的volatile有两个作用,一是保证可见性,其他线程读取的都是最新值;二是防止指令重排序导致对象未初始化完全。

2.2.3、类的内部类

public class SingularDemo4 {public static class Handle{public static SingularDemo4 instance = new SingularDemo4();}public SingularDemo4() {System.out.println("SingularDemo4");}public static SingularDemo4 getInstance(){return Handle.instance;}
}

类的内部类是靠JVM来保证并发的正确性,也就是一个类的构造方法在多线程下可以被正常的加载。我们永远可以相信JVM。

那问题来了?JVM是如何保证多线程下类的构造方法只会被执行一次的呢?

1、类加载机制和初始化锁

        当类加载器加载类时,JVM会使用类加载锁(Initialization Lock)来确保在同一时刻只有一个线程对类进行加载和初始化操作。

        一个线程开始初始化一个类时,其他线程需要等待这个类初始化完成,这是因为类的初始化过程中可能会执行静态代码块或静态变量赋值等操作,保证这些操作的互斥性能够避免多线程环境下的干扰。

2、Happens-Before

        在类的初始化过程中,对静态变量的赋值操作在对象引用发布之前必须完成,这意味着在静态变量赋值完成之前,其他线程不会看到不完整或部分初始化的对象。

2.2.4、使用CAS

public class SingularDemo7 {private static final AtomicReference<SingularDemo7> instance = new AtomicReference<>();private SingularDemo7(){System.out.println("singularDemo7");}public static SingularDemo7 getInstance() {while (true) {SingularDemo7 singularDemo7 = instance.get();if(singularDemo7 != null)   return singularDemo7;instance.compareAndSet(null, new SingularDemo7());return instance.get();}}
}

使用CAS方式保证单例,好处是不用加锁,效率更高;缺点也很明显,竞争激烈的情况下,可能大家都没法玩,一直循环,一直失败重试……

2.2.5、使用枚举类

public enum SingularDemo8 {instance;
}

Effective Java 作者推荐使用枚举的方式解决单例模式。

三、小结

        单例模式在实际的开发中应用的很多,它确保一个类只有一个实例,且这个实例的构造方法是私有的,并提供一个全局访问点来获取该实例。

        实现方式大致可以分为饿汉类和懒汉类,饿汉就是程序启动时就加载,这样有可能导致没有被用到的类也加载了,导致了性能的浪费;懒汉式的加载是用到就加载,不用不加载,但是要注意的是线程安全问题,合理的使用synchronized和volatile来保证线程安全。

        推荐使用的方式是双重校验锁和类的内部类,这两种方式既能保证懒加载,也能保证不会因为加锁或者锁的粒度较大导致的性能浪费。

相关文章:

  • C++ 继承和派生
  • JAXB的XmlElement注解
  • 紫色调城市和奔跑人物剪影背景工会工作总结汇报PPT模板
  • RabbitMQ 部署及配置详解(集群部署)
  • VB.net WebBrowser网页元素抓取分析方法
  • HMM与LTP词性标注之依存句法分析、词性标注实现
  • 在国内购买GPT服务前的一定要注意!!!
  • Git企业开发级讲解(五)
  • 操作系统(存储管理进程管理设备管理)
  • WPF中行为与触发器的概念及用法
  • 前端算法面试之堆排序-每日一练
  • 【C语言】自定义类型:结构体、枚举、联合
  • 竞赛选题 深度学习验证码识别 - 机器视觉 python opencv
  • 博客系统页面设计
  • 侧面多级菜单(一个大类、一个小类、小类下多个物体)
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • Android Volley源码解析
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • extract-text-webpack-plugin用法
  • spring + angular 实现导出excel
  • Spring-boot 启动时碰到的错误
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 批量截取pdf文件
  • 强力优化Rancher k8s中国区的使用体验
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 使用 @font-face
  • 算法之不定期更新(一)(2018-04-12)
  • 小程序01:wepy框架整合iview webapp UI
  • 用Canvas画一棵二叉树
  • 最简单的无缝轮播
  • 阿里云API、SDK和CLI应用实践方案
  • 从如何停掉 Promise 链说起
  • #git 撤消对文件的更改
  • #pragma once
  • #宝哥教你#查看jquery绑定的事件函数
  • $.ajax()方法详解
  • ${factoryList }后面有空格不影响
  • (1)bark-ml
  • (12)目标检测_SSD基于pytorch搭建代码
  • (2022 CVPR) Unbiased Teacher v2
  • (超详细)语音信号处理之特征提取
  • (规划)24届春招和25届暑假实习路线准备规划
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (转)【Hibernate总结系列】使用举例
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • (转)c++ std::pair 与 std::make
  • ***利用Ms05002溢出找“肉鸡
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • /var/log/cvslog 太大
  • [ C++ ] STL_stack(栈)queue(队列)使用及其重要接口模拟实现
  • [ linux ] linux 命令英文全称及解释
  • [ 蓝桥杯Web真题 ]-Markdown 文档解析