设计模式-单例模式

2023-05-16

本文章参考慕课DocMike老师的讲解,作为个人笔记,也希望能帮到需要的人

1.单例模式

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

1)饿汉模式

饿汉模式的3个特点

1、有一个private的构造函数(不会在系统中的其他代码内被实例化)

2、mInstance的变量是static的

3、getInstance的方法也是static的(向外部提供获取单例的方法)

不足之处:无法对instance实例做延时加载(instance 在类装载时就实例化)

    因为有时候单例的创建过程会很慢,变量初始化内容特别多。

    因为已经把这个单例变量设置为static,所以在JVM虚拟机加载单例这个类的时候,单例对象就会被建立。如果这个时候,这个单例类在系统中还扮演其他角色,比如说还会被其他的场景初始化,那么在任何使用这个单例类的地方,都会初始化这个沉重的单例变量,而不管是否被用到。

    instance会初始化很多的数据,导致他的static变量会被重复的加载

优化:懒汉模式

Code:

package singleton;
/**
* 饿汉模式
* @author stoneWang_L
*缺点:无法对mHungrySingleton这个变量做延时加载,因为单例的变量初始化有可能有很多变量
*/
public class HungrySingleton {
       private static final HungrySingleton mHungrySingleton = new  HungrySingleton();
       
       private HungrySingleton() {
             System.out.println("Singleton is create");
       }
       
       public static HungrySingleton getHungrySingleton() {
             return mHungrySingleton;
       }
       
       public static void createString() {
             System.out.println("createString in Singleton");
       }
       
       public static void main(String[] args) {
             HungrySingleton.createString();
       }
}

2)懒汉模式

优点:懒汉模式完成了延时加载的功能

不足之处:

    在多线程并发下这样的实现是无法保证实例唯一的。

优化:懒汉线程安全

package singleton;
public class LazySingleton {
       private static LazySingleton mInstance = null;
       
       private LazySingleton() {
       }
       
       public static LazySingleton getInstance() {
             //第一次调用的时候会被初始化
             if (null == mInstance) {
                    mInstance = new LazySingleton();
             }
             return mInstance;
       }
       
       public static void createString() {
             System.out.println("create String");
       }
       
       public static void main(String[] args) {
             LazySingleton.createString();
       }
}

在多线程并发下,懒汉是完全失效的。

package singleton;
public class MyThread extends Thread{
       @Override
       public void run() {
             System.out.println(LazySingleton.getInstance().hashCode());
       }
       
       public static void main(String[] args) {
             MyThread[] mts = new MyThread[10];
             for (int i=0; i<mts.length; i++) {
                    mts[i] = new MyThread();
             }
             
             for (int j=0; j<mts.length; j++) {
                    mts[j].start();
             }
       }
}

结果,hashCode值不一致

13676004
5506328
5506328
5506328
13676004
33221309
33221309
5506328
5506328
5506328

失效的原因:LazySingleton的getInstance方法不是同步的。

    所以在多线程的环境下,如果一个线程A正在创建一个LazySingleton,在他完成赋值操作以前,线程2可能正好判断这个instance为空,然后线程2也会走进创建LazySingleton,这个时候导致多个实例被创建,也印证了它的HashCode值是不一样的。

3)懒汉线程安全

优点:保证了懒汉模式下的线程安全

不足:影响性能(对instance判空都需要先同步,浪费时间)

    synchronied虽然能保证线程安全,但是非常影响性能,因为只能有一个线程去读取里面的数据,这样读取实例的时候,另外的线程就不能读取。

优化:DCL(双重检查锁机制)

code:

package singleton;
public class LazySafetySingleton {
       private static LazySafetySingleton instance;
       
       private LazySafetySingleton() {
       }
       
       public static void createString() {
             System.out.println("create String");
       }
       
       public static void main(String[] args) {
             LazySafetySingleton.createString();
       }
       
       //方法一:方法名中声明synchronized关键字,这样就可以保证,出现非线程安全问题,由于多个线程可以同时进入getInstance方法,这里使用了synchronized关键字保证只有一个线程可以进入getInstance方法,这样的懒汉单例是线程安全的
       public static synchronized LazySafetySingleton getInstance() {
             if (instance == null) {
                    instance = new LazySafetySingleton();
             }
             return instance;
       }
       
       //方法二:同步代码块实现。不在方法中声明,而是锁住LazySafetySingleton.class这个对象来保证它的线程安全
//     public static LazySafetySingleton getInstance() {
//           synchronized (LazySafetySingleton.class) {
//                  if (instance == null) {
//                         instance = new LazySafetySingleton();
//                  }
//           }
//           return instance;
//     }
}

4)DCL(double-checked locking,双重检查锁机制)

优点:解决了懒汉的线程安全和性能上的缺点,但是

不足之处:JVM的即时编译器中存在指令重排序的优化

解决办法:把instance变量声明为volatile

优化:静态内部类/枚举

code:

package singleton;
public class DclSingleton {
       private static volatile DclSingleton mInstance = null; //使用volatile修饰,禁止JVM指令重排序
//     private static DclSingleton mInstance = null;
       private DclSingleton() {}
       
       public void doSomething() {
             System.out.println("do sth.");
       }
       
       public static DclSingleton getInstance() {
             //避免不必要的同步
             if (mInstance == null) {
                    //同步
                    synchronized (DclSingleton.class) {
                           //在第一次调用时初始化
                           if (mInstance == null) {
                                 /*这一步看似完美,实则不是原子操作,不是原子操作意味着JVM会做这样一个事。
第一步会给instance分配内存,第二步调用DclSingleton的构造方法来初始化变量,第三步将mInstance这个对象指向我们JVM分配的内存空间,但是JVM有这样一个缺点,在即时编译器中存在指令重排序的优化。
就是说以上的3步不会按照我们想要的顺序执行,有可能第三步在第二步之前,有可能第二步在第一步之前。
所以这个时候就会造成线程的不安全,也会造成报错。
解决方法:将instance方法设置成volatile就行了,它能保证本地不会存在instance的副本,而每次都会到内存中去读取,实际上完全是。
实际上使用volatile关键字可以禁止JVM的指令重排序优化,原来的三步顺序就不会变了。
另外volatile修饰的这个变量,它的赋值操作会有一个内存屏障,读操作的时候不会被被重排序到内存屏障之前*/
                                 mInstance = new DclSingleton();
                           }
                    }
             }
             return mInstance;
       }
}

5)静态内部类

静态内部类

    JVM给我们提供了同步控制,什么时候提供的:其实JVM提供给我们2个关键字,static,final

    首先,我们可以通过static进行区块的初始化数据,这个时候就保证我们的数据在内存中是独一份的,final字段访问的时候,由于final字段修饰的变量初始化完成之后,他就无法被修改,所以final字段也是线程安全的。

    这就是JVM提供给我们的同步控制。

优点:JVM本身的机制保证了线程安全/没有性能缺陷

原因:static/final

三个优点:

1、JVM虚拟机本身的机制保证了线程安全

2、并没有使用关键字synchronized,

    synchronied虽然能保证线程安全,但是非常影响性能,因为只能有一个线程去读取里面的数据,这样读取实例的时候,另外的线程就不能读取。

    而静态内部类就不一样,他可以同时读取实例,这样就提高了我们的性能。

3、SingletonHolder这个类是私有的

    除了getInstance方法,没有其他的地方能访问他。

code:

package singleton;
public class StaticInnerSingleton {
       private StaticInnerSingleton() {
       }
       
       public static StaticInnerSingleton getInstance() {
             return SingletonHolder.sInstance;
       }
       
       //静态内部类
       private static class SingletonHolder {
             private static final StaticInnerSingleton sInstance = new  StaticInnerSingleton();
       }
}

利用了内部类的原理,在内部类中去创建对象的实例。这样只要我们的业务中不使用这个内部类StaticInnerSingleton,JVM虚拟机就不会去加载这个类,也就不会去创建我们所要创建的单例对象instance。从而这里的静态内部类就完成了懒汉式的延时加载,同时通过static内部类完成了线程安全。

    第一次加载StaticInnerSingleton,不会初始化instance,只有第一次调用getInstance这个方法的时候,虚拟机才会加载SingletonHolder并初始化我们的instance。这样不仅能确保线程安全,还能确保single类的唯一性。

    所以这种写法又简单又能做到懒汉式的延迟加载,同时他也是线程安全的。所以面试的时候,可以不写饿汉,懒汉或者DCL,直接写静态内部类的形式。它实现的重要思想是利用了静态变量的唯一性。

6)枚举

枚举的概念在java5才出现。枚举的单例模式只能在java5之后才能出现

优点:写法简单/线程安全(如果不写自己的实例,枚举的实例默认是线程安全的)

code

package singleton;
public enum EnumSingleton {
       //定义一个枚举的元素,他就是Singleton的一个实例
       INSTANCE;
       
       public void doSomeThing() {
             
       }
}

如果只写INSTANCE,那INSTANCE是线程安全的。要是在doSomeThing中写一些实例方法,一定要确保是线程安全的,因为他会影响其他对象的。

    默认情况下枚举类是线程安全的,如果要在枚举类中添加自己的实例方法,一定要做好线程安全的操作。

    枚举类最大的特点就是它的写法特别的简单,相较于静态内部类还要简单。

2、单例模式-Android中运用

1、Application

application是单例的,而且生命周期是整个Android应用程序中最长的,它的生命周期等同于整个程序的生命周期

code:获取全局的context

public class MusicAppUtils extends Application{
    private static Context sContext;

    @Override
    public void onCreate() {
        super.onCreate();
        sContext = this;
    }

    public static Context getContext(){
        return sContext;
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计模式-单例模式 的相关文章

  • 常见数据库版本

    SQL Server安装包 考虑到好些新手同学不知道怎么下载自己所需的数据库软件安装包 xff0c 特分享一些我收藏的安装包 现分享SQL Server的一些安装包给大家 根据自身需求下载 xff0c 新手朋友建议下载SQL Server
  • 思维导图工具集

    浏览所有国外资源时使用可以实时网页翻译的浏览器 例如Google Chrome QQ浏览器 360浏览器 Firefox浏览器等 xmind 功能 XMind 一个功能齐全的思维导图和头脑风暴工具 旨在产生想法 激发创造力 提高工作和生活的
  • 自动化运维一体化

    运维一体化中的平台一体化 xff0c 指的是运维一体化与平台一体化 xff0c 其中运维一体化是数据中心在运维方面的运营体系 xff0c 它包括三方面 xff1a 人员组织一体化 流程一体化 平台一体化 一 转型 xff1a 和目前大部从运
  • 技术文档写作

    科技文排版技巧 目录 一 段落格式的设定 2 二 设置标题 3 三 利用 导航 选项 xff0c 对论文进行快速定位 xff08 建立在3的基础上 xff09 4 四 插入 分页符 和 分节符 5 五 页码的设置 6 六自动目录生成 xff
  • 资料目录三

    需要更多资料请添加微信 xff1a Stestack 毕设项目 美食美刻网站制作 视频http yun itheima com course 253 html 资料链接 xff1a http pan baidu com s 1dFkYBXv
  • VMware虚拟机ubuntu显示屏幕太小解决办法

    1 安装VMware Tool需要用到虚拟光驱 xff0c 加载一个ISO文件 2 从 media 中复制到 opt下 3 击ubuntu左侧列表里的DVD图标 xff0c 就会出现VMware Tools的安装文件VMwareTools
  • 面向edas开发的规范

    前言 概述 本文档 主要介绍了 服务开发者基于Aliware做项目开发时 xff0c 从项目环境搭建 xff0c 项目开发 xff0c 项目上线及Aliware 服务功能相关的使用 说明 xff0c 操作步骤及代码示例 应用范围 服务开发者
  • 用 Python 进行金融数据可视化

    用 Python 进行金融数据可视化 Python量化的关键是金融数据可视化 xff0c 无论是传统的K线图 xff0c 还是现在的策略分析 xff0c 都需要大量的可视化图表 具体到编程代码 xff0c 就是使用Python绘图模块库绘图
  • Ubuntu双屏的副屏抖动闪烁

    将设置按如下即可 xff1a
  • 日志切割的方法

    一 关于日志切割 日志文件包含了关于系统中发生的事件的有用信息 xff0c 在排障过程中或者系统性能分析时经常被用到 对于忙碌的服务器 xff0c 日志文件大小会增长极快 xff0c 服务器会很快消耗磁盘空间 xff0c 这成了个问题 除此
  • 软件测试的四个阶段

    软件测试的对象包括软件需求 概要设计 详细设计 软件运行环境 可运行程序和软件源代码等 软件测试包括质量 人员 资源 技术和流程五大要素 xff0c 以及测试覆盖率和测试效率两个目标 软件测试一般分为4个阶段 xff1a 单元测试 集成测试
  • Rabbitmq实现多系统间的分布式事务,保证数据一致性

    Rabbitmq实现多系统间的分布式事务 xff0c 保证数据一致性 一 实验环境二 实验目的三 实验方案四 实验步骤1 消息队列1 1 rabbitmq安装过程略过 1 2 创建订单交换器 xff1a orderExchange1 3 创
  • Android -Lottie加载动画喂饭指南

    什么是Lottie 简单的说 xff0c Lottie就是airbnb开源的一个使用json文件快速加载动画且支持多平台的库 更多介绍请查看官网 官网地址 xff1a https airbnb design lottie 怎么使用Lotti
  • Ubuntu安装汉化版Portainer(Docker图形UI界面)

    1 找镜像 docker search dockerui NAME DESCRIPTION STARS OFFICIAL AUTOMATED abh1nav dockerui An updated version of crosbymich
  • 解决Deepin、统信UOS开机出现引导错误

    开机后GRUB提示错误 错误提示 xff1a error unknown filesystem 原因分析 xff1a 对硬盘进行分区后 xff0c 导致原先的分区发生位置变化 xff0c 比如sda6分区变成了sda7分区 这时候 xff0
  • 2022年了,Windows Vista还能用吗?

    今年试用了一段时间Windows11 xff0c 老实说总觉得差点意思 怎么说呢 xff0c 现在的Windows简直就是Linux 43 macOS的缝合怪 xff1a 任务栏越来越像苹果Dock xff1b 浏览器Edge用上Chrom
  • Windows11硬盘读写速度变慢的解决方法

    解决方法 命令提示符 xff0c 以管理员身份打开 fsutil usn 查询与USN日志相关的命令 deleteJournal 删除 USN 日志 用法 fsutil usn deleteJournal lt 标志 gt lt 卷路径名称
  • Ubuntu 22.04 安装 VMware Workstation 16

    基本安装 sudo chmod 43 x VMware Workstation Full 16 2 4 20089737 x86 64 bundle sudo VMware Workstation Full 16 2 4 20089737
  • 2018年了,Windows2000还能用吗?

    前两天偶尔在B 站看到一个宝岛小伙子阿哲录了一期在 2018 年用 Windows2000 的节目 xff0c 他花了一个礼拜时间体验虚拟机下使用 Windows2000 其实真的要日常使用 Windows2000 xff0c 估计是坚持不
  • flask jsonify TypeError: Object of type int64 is not JSON serializable

    写接口的时候 Google找了半天 xff0c 觉得都很麻烦 灵机一动 xff0c 想到了一个简单方法 问题的原因 字典中的数字被识别成了int64类型 xff0c json无法识别int64 解决方式 xff1a 先将字典转换成字符串再将

随机推荐