Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java 设计模式 | 单例模式

Java 设计模式 | 单例模式

作者头像
utopia
发布于 2023-03-20 09:23:47
发布于 2023-03-20 09:23:47
43500
代码可运行
举报
文章被收录于专栏:UtopiaUtopia
运行总次数:0
代码可运行

概述

单例模式,是设计模式中最常见的模式之一,它是一种创建对象模式,用于产生一个对象的具体实例,可以确保系统中一个类只会产生一个实例。

优缺点

优点

  1. 对于频繁使用的对象,可以省去 new 操作花费的时间,尤其对那些重量级对象而言,削减了一笔非常客观的系统开销。
  2. 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,从而减轻 GC 压力,缩短 GC 停顿时间。

缺点

  1. 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
  2. 并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
  3. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

场景

  • Spring 中创建的 Bean 实例默认都是单例。
  • 数据库连接池的设计与实现。
  • 多线程的线程池设计与实现。

核心结构

单例模式的核心在于通过一个接口返回唯一的对象实例。

常见写法

1.饿汉模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Singleton {private Singleton() {
        System.out.println("create Singleton");
    }private static Singleton instance = new Singleton();public static Singleton getInstance(){
        return instance;
    }
}

饿汉模式单例的实现方式简单,在 JVM 对类加载的时候,单例对象就会被创建,因此线程安全。由于获取实例的静态方法没有使用同步方法,调用效率高。但如果该实例从始至终都没被使用过,则会造成内存浪费。

2.懒汉模式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class LazySingleton {private LazySingleton() {
        System.out.println("create Singleton");
    }private static LazySingleton instance = null;public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

懒汉模式单例是对静态成员变量 instance 赋予初始值 null,确保系统启动时没有额外的负载。在第一次使用的时候才进行初始化,达到了懒加载的效果。由于获取实例的静态方法用 synchronized 关键字修饰,所以线程安全。但是由于每次获取实例都要进行同步加锁,因此效率较低。

3.双重检测机制(DCL)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class DCLSingleton {private DCLSingleton() {
        System.out.println("create Singleton");
    }private static volatile DCLSingleton instance = null;public static DCLSingleton getInstance() {
        if (instance == null) {
            synchronized (DCLSingleton.class) {
                if(instance == null){
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

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

CPU 内部会在保证不影响最终结果的前提下对指令进行重新排序(不影响最终结果只是针对单线程,切记),指令重排的主要目的是为了提高效率。

正常情况按顺序执行,双重检测机制是没有问题。如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
memory = allocate();  // 1.分配对象的内存空间
ctorInstance(memory); // 2.初始化对象
instance = memory;    // 3.设置 instance 指向刚才分配的内存地址

指令重排需后:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
memory = allocate();  // 1.分配对象的内存空间
instance = memory;    // 3.设置 instance 指向刚才分配的内存地址
ctorInstance(memory); // 2.初始化对象

如果线程 A 执行完 1 和 3,instance 对象还未完成初始化,但是已经不再指向 null。此时线程 B 抢占到 CPU 资源,执行第12 行的检测结果为 false,则执行第19行,从而返回一个还未初始化完成的 instance 对象,从而出导致问题出现。

使用 volatile 关键字修饰 instance 对象可以禁止指令重排序。

4.静态内部类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class StaticInnerHolderSingleton {private StaticInnerHolderSingleton(){
        System.out.println("create Singleton");
    }private static class InnerHolder{
        private static StaticInnerHolderSingleton instance = new StaticInnerHolderSingleton();
    }
    
    public static StaticInnerHolderSingleton getInstance(){
        return InnerHolder.instance;
    }
}

当 StaticInnerHolderSingleton 被加载时,内部类 InnerHolder 并不会被初始化,只有在 getInstance() 方法被调用时,才会加载 InnerHolder,从而初始化 instance,做到了延迟加载。

StaticInnerHolderSingleton 实例的创建在 Java 编译时期收集在 () 中,该方法又是同步方法,可以保证内存的可见性、JVM指令的顺序性以及原子性。

5.枚举

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public enum EnumSingleton {INSTANCE;EnumSingleton(){
        System.out.println("create Singleton");
    }// 调用getInstance方法,事实上获得Holder的instance静态属性
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }

枚举类型不允许被继承,同时是线程安全且只能被实例化一次,但是枚举类型不能够懒加载,对 EnumSingleton 主动使用,如用其中的静态方法 INSTANCE 会立即实例化。

通过 Java 反射机制或序列化和反序列化可能会破坏单例,但枚举模式的单例天然不存在这个问题。

单例破坏问题

通过 Java 反射机制,强行调用单例类的私有构造函数可以生成多个单例示例,这种情况相对极端,代码中也不会去如此实现。

对于序列化和反序列化,可以通过私有方法 readResolve() 解决这个问题,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class SerializableSingleton implements Serializable {private SerializableSingleton() {
        System.out.println("create Singleton");
    }private static SerializableSingleton instance = new SerializableSingleton();public static SerializableSingleton getInstance(){
        return instance;
    }private Object readResolve(){
        return instance;
    }
}

测试代码如下,可以自行测试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws Exception {
        SerializableSingleton s1  = null;
        SerializableSingleton s = SerializableSingleton.getInstance();
        // 先将实例序列化到文件
        FileOutputStream fos = new FileOutputStream("SerializableSingleton.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s);
        oos.flush();;
        oos.close();
        // 从文件反序列化读出原有的单例类
        FileInputStream fis = new FileInputStream("SerializableSingleton.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        s1 = (SerializableSingleton) ois.readObject();
        System.out.println(s.equals(s1));
    }

事实上,在实现了私有的 readResolve() 方法后,readObject() 方法就已经形同虚设,它直接使用 readResolve() 替换了原本的返回值,从而从形式上构造了单例。

总结

在实际工作中,单例的使用还是比较常见的,在几种实现方式中,双重检测机制、静态内部类、枚举方式都是比较推荐。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/08/26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
一个单例模式,被问7个问题,难!
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。单例模式属于创建型模式,它提供了一种创建对象的最佳方式。
田维常
2022/04/19
7980
一个单例模式,被问7个问题,难!
钻钻 “单例” 的牛角尖
上篇文章 走进 JDK 之 Enum 提到过,枚举很适合用来实现单例模式。实际上,在 Effective Java 中也提到过(果然英雄所见略同):
路遥TM
2021/08/31
4550
详解设计模式@单例的进化之路
单例模式(Singleton Pattern)是设计模式中一个重要的模式之一,是确保一个类在任何情况下都绝对只有一个实例。单例模式一般会屏蔽构造器,单例对象提供一个全局访问点,属于创建型模式。
堆栈哲学
2023/03/08
2410
详解设计模式@单例的进化之路
【设计模式-单例模式】
今天来说一下同样属于创建型模式的单例模式,相信这个模式普遍都清楚,因为平时在编码的时候都会进行相应的使用,我这边就当做日志记录一下。免得以后忘了还得去搜,我发现我记忆里非常差,很多东西很快就忘记了,年纪大了没办法。
Liusy
2020/09/01
5550
【设计模式-单例模式】
当Kotlin邂逅设计模式之单例模式(一)
简述: 从这篇文章开始,我将带领大家一起来探讨一下Kotlin眼中的设计模式。说下为什么想着要开始这么一个系列文章。主要基于下面几点原因:
bennyhuo
2020/02/20
9640
02、人人都会设计模式--单例模式
一个男人只能有一个媳妇「正常情况」,一个人只能有一张嘴,通常一个公司只有一个 CEO ,一个狼群中只有一个狼王等等
TigerChain
2019/07/22
5140
02、人人都会设计模式--单例模式
你知道几种单例模式?
http://www.jianshu.com/u/d5b531888b2b
陈宇明
2020/12/15
3830
设计模式(一)之单例模式
关键代码:构造函数是私有的。 应用场景: 1.一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。 2.创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。 3.Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 UML图:
周杰伦本人
2023/10/12
2350
设计模式(一)之单例模式
面向对象设计模式--单例模式详解+实际应用(Java)
保证了一个类只有一个实例,并且提供了一个全局访问点。单例模式的主要作用是节省公共资源,方便控制,避免多个实例造成的问题。
飞天葫芦侠
2023/03/20
2.3K0
面向对象设计模式--单例模式详解+实际应用(Java)
Java设计模式学习记录-单例模式
前言 已经介绍和学习了两个创建型模式了,今天来学习一下另一个非常常见的创建型模式,单例模式。 单例模式也被称为单件模式(或单体模式),主要作用是控制某个类型的实例数量是一个,而且只有一个。 单例模式 单例模式的实现方式  实现单例模式的方式有很多种,大体上可以划分为如下两种。 外部方式 在使用某些全局对象时,做一些“try-Use”的工作。就是如果要使用的这个全局对象不存在,就自己创建一个,把它放到全局的位置上;如果本来就有,则直接拿来使用。 内部实现方式 类型自己控制正常实例的数量,无论客户程序是否尝试过
纪莫
2018/07/04
3890
设计模式入门:单例模式
  单例模式属于创建型模式,是一种较为简单的设计模式,但也是最容易让人犯错的。在不同的单例模式实现中,首先要确保构造函数是私有的,然后提供一个静态入口(方法)用于获取唯一的实例。   大多数情况下,不建议使用非线程安全的以及synchronized监视器锁实现的懒汉方式,在资源允许的情况下尽可能使用饿汉模式。如果明确要实现 lazy loading 效果时,可以使用静态内部类形式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用DCL双检锁的方式。
happyJared
2018/09/20
4380
设计模式入门:单例模式
单例模式的迭代式优化过程
在软件设计架构中,单例模式是最为常用的一种设计模式,所谓单例模式是指在创建某一个类的对象实例时该系统中有且仅有该类的一个实例,从而可以合理解决实例化对象的性能开销、资源分配等问题。从实现角度看,单例模式主要分为两种,一般称为饿汉式单例模式和懒汉式单例模式,以下逐一介绍
用户7506105
2021/08/09
3110
摸鱼设计模式——单例模式
饿汉式单例,无论是否使用,都直接初始化。其缺点则是会浪费内存空间。因为假如整个实例都没有被使用,那么这个类依然会创建,这就白创建了。
摸鱼-Sitr
2021/01/04
6730
摸鱼设计模式——单例模式
单例设计模式源码分析,常用设计模式白话文总结 顶
通过该错误说明,该枚举类中无参数构造函数 2.使用java反编译技术,查看枚举类
须臾之余
2019/07/05
4320
单例设计模式源码分析,常用设计模式白话文总结
                                                                            顶
设计模式 | 单例模式及典型应用
单例是最常见的设计模式之一,实现的方式非常多,同时需要注意的问题也非常多。要内容:
小旋锋
2019/01/21
1K0
三、单例模式详解
2、单例模式是非常经典的高频面试题,希望通过面试单例彰显技术深度,顺利拿到Offer的人群。
编程之心
2020/08/12
9130
三、单例模式详解
Java-单例模式
单例模式是我们实际开发中常用到的开发模式,目的是保证实例的唯一性,确保这个类在内存中只会存在一个对象,但我们现在用到的单例模式相关代码可能不是最优的,今天让我们探索一下单例模式的正确写法。 单例模式通常分为饿汉式和懒汉式,我们这里来一个最简单的代码: 饿汉式相关代码:
android_薛之涛
2018/12/27
5520
【设计模式】之单例模式
单例模式属于管理实例的创造型类型模式。单例模式保证在你的应用种最多只有一个指定类的实例。
青山师
2023/05/05
2790
聊一聊最难的设计模式 - 单例模式
很多人上来肯定一脸懵逼,因为在你的印象中,单例模式实现起来还是很简单的。不要着急,慢慢往下看,你就知道为什么我说它最难了。 1. 基本概念 单例模式是一种常用的创建型设计模式。单例模式保证类仅有一个实例,并提供一个全局访问点。 2. 适用场景 想确保任何情况下都绝对只有一个实例。 典型的场景有:windows 的任务管理器、windows 的回收站、线程池的设计等。 3. 单例模式的优缺点 优点 内存中只有一个实例,减少了内存开销。 可以避免对资源的多重占用。 设置全局访问点,严格控制访问
山海散人
2021/03/03
2890
聊一聊最难的设计模式 - 单例模式
Java设计模式之单例模式
一般单例模式口诀:两私一公。 具体说就是私有构造方法、私有静态实例、公开的静态获取方法。
程裕强
2022/05/06
2590
Java设计模式之单例模式
相关推荐
一个单例模式,被问7个问题,难!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验