概述

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,使用单例模式的类只有一个对象实例。

单例应用场景

  1. Windows系统的任务管理器。
  2. Windows系统的回收站。
  3. 操作系统的文件系统,一个操作系统只能有一个文件系统。
  4. 数据库连接池的设计与实现。
  5. 多线程的线程池设计与实现。
  6. Spring中创建的Bean实例默认都是单例。
  7. Java-Web中,一个Servlet类只有一个实例。
  8. 等等...

单例的实现要点

单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法)。

单例的实现主要是通过以下三个步骤:

  1. 将类的构造方法定义为私有方法。这样其他类的代码就无法通过调用该类的构造方法来实例化该类的对象,只能通过该类提供的静态方法来得到该类的唯一实例。
  2. 定义一个私有的类的静态实例。
  3. 提供一个公有的获取实例的静态方法。

单例追求的目标

  1. 线程安全。
  2. 懒加载。
  3. 调用效率高。

单例模式的常用写法

常用的单例有以下5种写法,如果还有其他的写法,也基本是从以下5种稍微修改而来,由于内容基本一致,并且可能不是很常用,因此在本文中不再赘述。

1.饿汉模式

饿汉模式,比较常见的一种写法。在类加载的时候就对实例进行初始化,没有线程安全问题;获取实例的静态方法没有使用同步,调用效率高;但是没有使用懒加载,如果该实例从始至终都没被使用过,则会造成内存浪费。

总结:线程安全、非懒加载、效率高。

是否推荐:可以使用,但不推荐。

2.懒汉模式

线程安全的懒汉模式,比较常见的一种写法。在第一次使用的时候才进行初始化,达到了懒加载的效果;由于获取实例的静态方法用synchronized修饰,所以也没有线程安全的问题;但是,这种写法每次获取实例都要进行同步(加锁),因此效率较低,并且可能很多同步都是没必要的。

总结:线程安全、懒加载、效率低。

是否推荐:可以使用,但不推荐。

注:该模式还有另一种常见写法,就是把getInstance方法上的synchronized去掉,这种方法有线程安全问题,不能使用。

3.双重检测机制(DCL)

双重检测机制(双重检查加锁),比较常见的一种写法。在第一次使用的时候才进行初始化,达到了懒加载的效果;在进行初始化的时候会进行同步(加锁),因此没有线程安全问题;并且只有第一次进行初始化才进行同步,因此不会有效率方面的问题。

《Java Concurrency in Practice》作者Brian Goetz在书中提到关于DCL的观点:促使DCL模式出现的驱动力(无竞争同步的执行速度很慢,以及JVM启动时很慢)已经不复存在,因而它不是一种高效的优化措施。延迟初始化占位类模式(静态内部类)能带来同样的优势,并且更容易理解。

总结:线程安全、懒加载、效率高。

是否推荐:可以使用。

注:该模式还有另一种常见写法,就是把静态实例singleton的volatile修饰去掉,这种方法有线程安全方面的问题,不能使用。在我的另一篇文章有提到这个:volatile关键字详解,这里直接截取该部分内容,请见下面这个例子。

例子:双重检测机制实现单例(没有volatile修饰)

这段代码是单例的双重检测机制实现,相信很多人都用过,并且觉得这个代码是没问题的。在大多数情况,这段代码确实没问题,但在极端的情况下,有个隐藏的问题。

例子分析:

假设有两个线程同时访问这段代码,此时线程A走到15行开始初始化对象,线程B则刚走到12行进行第一次检测。这时要介绍下15行初始化这行代码,这行代码虽然只有一句话,但是被编译后会变成以下3条指令:

正常情况下,这3条执行时按顺序执行,双重检测机制就没有问题。但是CPU内部会在保证不影响最终结果的前提下对指令进行重新排序(不影响最终结果只是针对单线程,切记),指令重排的主要目的是为了提高效率。在本例中,如果这3条指令被重排成以下顺序:

如果线程A执行完1和3,instance对象还未完成初始化,但是已经不再指向null。此时线程B抢占到CPU资源,执行第12行的检测结果为false,则执行第19行,从而返回一个还未初始化完成的instance对象,从而出导致问题出现。要解决这个问题,只需要使用volatile关键字修饰instance对象即可。

4.静态内部类(延迟初始化占位类)

静态内部类(延迟初始化占位类),比较常见的一种写法。JVM将推迟SingletonHolder的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化Singleton,因此不需要额外的同步。当任何一个线程第一次调用getInstance时,都会使SingletonHolder被加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。

通过静态初始化来初始化Singleton为什么不需要额外的同步?

在初始器中采用了特殊的方式来处理静态域(或者在静态初始化代码块中初始化的值),并提供了额外的线程安全性保证。静态初始化器是由JVM在类的初始化阶段执行,即在类被加载后并且被线程使用之前。由于JVM将在初始化期间获得一个锁,并且每个线程都至少获取一次这个锁以确保这个类已经加载,因此在静态初始化期间,内存写入操作将自动对所有线程可见。因此无论是在被构造期间还是被引用时,静态初始化的对象都不需要显式的同步。然而,这个规则仅适用于在构造时的状态,如果对象是可变的,那么在读线程和写线程之间仍然需要通过同步来确保随后的修改操作是可见的,以及避免数据破坏。

总结:线程安全、懒加载、效率高。

是否推荐:推荐使用、《Java Concurrency in Practice》作者Brian Goetz推荐使用的方式。

5.枚举

枚举,不是很常见的一种写法。很简洁的一种实现方式,提供了序列化机制,保证线程安全,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。

总结:线程安全、非懒加载、效率高。

是否推荐:推荐使用、《Effective Java》作者Joshua Bloch推荐使用的方式。

几种方式对比:

可能有人看了以上表格,觉得枚举有缺点,为什么Joshua Bloch还推荐使用枚举?

这就要提到单例的破解了。普通的单例模式是可以通过反射和序列化/反序列化来破解的,而Enum由于自身的特性问题,是无法破解的。当然,由于这种情况基本不会出现,因此我们在使用单例模式的时候也比较少考虑这个问题。

总结:

  1. 单例无论是在项目的开发中,还是面试中都是非常常见的,因此需要熟练的掌握好单例的知识。只要记住单例的三个实现要点——私有构造方法、定义静态Singleton实例、暴露实例获取方法,手写这5种实现方式相信都是信手拈来。如果是面试的时候,建议使用静态内部类的实现。
  2. 项目中具体使用哪种实现方式可以根据情况而定,个人推荐的方式是:静态内部类和枚举,我自己在项目中常见的也是这两种方式,可能静态内部类会更多一点。另外就是毕竟是两位大神推荐的,还考虑什么,直接用就是了。

推荐阅读

程序员囧辉:字节、美团、快手核心部门面试总结(真题解析)​zhuanlan.zhihu.com

程序员囧辉:4 年 Java 经验面试总结、心得体会​zhuanlan.zhihu.com

程序员囧辉:面试阿里,HashMap 这一篇就够了​zhuanlan.zhihu.com

程序员囧辉:面试必问的线程池,你懂了吗?​zhuanlan.zhihu.com

程序员囧辉:BATJTMD 面试必问的 MySQL,你懂了吗?​zhuanlan.zhihu.com

静态内部类实现单例_单例模式详解相关推荐

  1. 单例模式(单例设计模式)详解

    在有些系统中,为了节省内存资源.保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式. 单例模式的定义与特点 单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自 ...

  2. 静态内部类实现单例_为什么用枚举类来实现单例模式越来越流行?

    前言 单例模式是 Java 设计模式中最简单的一种,只需要一个类就能实现单例模式,但是,你可不能小看单例模式,虽然从设计上来说它比较简单,但是在实现当中你会遇到非常多的坑,所以,系好安全带,上车. 单 ...

  3. 2.5万字详解23种设计模式—创建型模式(简单工厂、工厂方法、抽象工厂、单例-多线程安全详解、建造者、原型)的详细解读、UML类图

    本文简述了各大设计模式,并通过UML和代码详细说明.本文大约共 2.5W 字,建议收藏.下方是本文的目录: 一.设计模式的认识 二.设计模式的分类 根据其目的 根据范围 三.设计模式的优点 四.设计模 ...

  4. java 缓存 单例_单例模式应用:高速缓存和查找对象(转)

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 单例模式获得工厂唯一实例,用map保存对象引用,实现快速查找 1.例子 package com.d1zhan.cache; import java.uti ...

  5. filter java 是单例的吗_JAVA 设计模式之 单例模式详解

    单例模式:(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点.单例模式是创建型模式.单例模式在现实生活中应用也非常广泛. 在 J2EE 标准中,S ...

  6. java connection 单例_Java设计模式之单例模式详解

    Java设计模式之单例模式详解 什么是设计模式 设计模式是在大量的实践中总结和理论之后优选的代码结构,编程风格,以及解决问题的思考方式.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可 ...

  7. 单例模式应用场景_三、单例模式详解

    4.单例模式详解 4.1.课程目标 1.掌握单例模式的应用场景. 2.掌握IDEA环境下的多线程调试方式. 3.掌握保证线程安全的单例模式策略. 4.掌握反射暴力攻击单例解决方案及原理分析. 5.序列 ...

  8. 以下属于单例模式的优点的是_三、单例模式详解

    4.单例模式详解 4.1.课程目标 1.掌握单例模式的应用场景. 2.掌握IDEA环境下的多线程调试方式. 3.掌握保证线程安全的单例模式策略. 4.掌握反射暴力攻击单例解决方案及原理分析. 5.序列 ...

  9. java中的单例_细说Java中的几种单例模式

    在Java中,单例模式分为很多种,本人所了解的单例模式有以下几种,如有不全还请大家留言指点: 饿汉式 懒汉式/Double check(双重检索) 静态内部类 枚举单例 一.饿汉式 image 饿汉式 ...

最新文章

  1. 让你的JS代码更具可读性
  2. Cisco Easy ***综合配置示例
  3. python对excel某一列去重-「总结篇」Python中所有的Excel操作技巧
  4. 重温WCF之会话Session(九)
  5. mysql 中default 和NULL
  6. java中jdbc的封装笔记_JDBC封装学习笔记(三)---面向对象的JDBC,使用preparedStatement...
  7. 四、数据挖掘中常见的挖掘模式
  8. 多元高斯分布(Multivariate Gaussian Distribution)
  9. 解析5G背后的核心技术:波束成形
  10. 【codevs4654】【BZOJ2442】修剪草坪,第一次的单调队列,优化DP
  11. 【Java】数据库编程
  12. Java中的锁---队列同步器
  13. 【洛谷】P1216数字三角形
  14. 基于MATLAB的运动车辆跟踪检测系统
  15. 计算机中插入背景图片怎样操作,如何将图片设置为Word页面背景?
  16. 分解成质因数(如435234=251*17*17*3*2
  17. 二本电气工程应届生收割5个offer,转型大数据真的与专业无关
  18. PS如何能让照片背景虚化
  19. MySQL使用INTO OUTFILE和LOAD DATA INFILE导出导入百万级数据文件
  20. 期货开户手机APP有哪些?

热门文章

  1. jQuery 效果 – 淡入淡出
  2. 入门Linux系统编程--网络编程
  3. input内容部分文本不可删除
  4. 对MySQL的查询生成报表(初学者新尝试)
  5. xml文件解析 (DOM4J解析XML) -java
  6. 【微信小程序】关于微信小程序获取手机号的问题
  7. C#编写定制的HID调试助手
  8. 安卓开发快速上手!优秀Android程序员必知必会的网络基础,含爱奇艺,小米,腾讯,阿里
  9. jq锚点定位平滑跳转和导航跟随页面滚动并定位到相应位置,导航高亮显示
  10. 【Unity实战100例】---Unity制作360全景移动端陀螺仪交互