单例各样方式的写法

单例简介

特点

  • 内存中只有一个实例,节约内存,无需频繁创建,减少性能开销,提高系统运行效率
  • 使用者无需关心类创建过程,整个项目中任何地方、任何时间开箱即用

缺点

  • 单例模式没有抽象,扩展会有很大困难
  • 单例类的职责过重,违背了“单一职责原则”

适用场景

  • 适用于全局共享变量、方法,如统计在线人数、对接第三方Client等
  • 常用配置和工具类如各种Config、Properties、JSONUtil、HTTPUtil等

单例常用方法

1. 饿汉模式

- 静态方法

public class Singleton {
    private  final static Singleton singleton = new Singleton();
    private Singleton(){    }

    public static Singleton getInstance() {
        return singleton;
    }
}

- 静态代码块

public class Singleton {
    private  final static Singleton singleton;
    static {
        singleton = new Singleton();
    }
    private Singleton(){ }

    public static Singleton getInstance() {
        return singleton;
    }
}

小结

这两种方法使用比较简单,平时开发中用的也比较多。

优点

  • 使用简单、快速上手、天然线程安全,由JVM保证每个类在内存中只有一个实例

缺点

  • 不管是否用到,都会实例化,那么问题来了不使用为什么要创建

2. 懒汉模式

- 按需加载

public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

- 线程安全

public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }
    //线程安全
    public synchronized static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

- 双重检查锁DCL(Double Checked Lock)

public class Singleton {
    // 私有实例,volatile关键字,禁止指令重排。
    private volatile static Singleton instance;
    private Singleton() {}   
    // 公共获取实例方法(线程安全)
    public static Singleton getInstance() {
        //当instance不为null时大部分调用都没有锁,直接返回,性能好
        if(instance == null ) { 
            // 一重检查不加锁提高并发性能
            synchronized (Singleton.class) {
                if(instance == null) { 
                    // 二重检查确保创建线程安全
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

小结

懒汉模式是针对饿汉模式不能按需加载进行的改进,第一种改进出现了线程不安全于是出现了第二种线程安全的方法,第二种加锁后线程安全又导致性能比较差,所以出现了第三种DCL双重检查锁方式。

优点

  • 解决了饿汉模式不能按需加载的问题

缺点

  • 出现了线程不安全、加锁性能差等问题,然后又打补丁,代码冗余

3. 基于内部类

内部类静态方法

 
public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }
    private static class SingletonHolder {
        private static Singleton singleton=new Singleton();
    }
    public  static Singleton getInstance() {
        return SingletonHolder.singleton;
    }
}

内部类静态代码块

public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }
    private static class SingletonHolder {
        private static Singleton singleton;
        static {
            singleton = new Singleton();
        }
    }
    public  static Singleton getInstance() {
        return SingletonHolder.singleton;
    }
}

小结

基于内部类即解决了按需加载的问题,可以延迟加载,又解决了加锁导致的性能问题,是对饿汉模式和懒汉模式取其精华,去其糟粕,推陈出新,革故鼎新最好的体现。在我的一种适合容器化部署的雪花算法ID生成器一文中就使用该方法,延时加载获取到了REDIS_ID。

优化

  • 使用时实例化对象,可以延迟加载
  • 无锁,性能好

缺点

  • 写法稍微复杂了点

4. 枚举

枚举单例

 

public enum  Singleton {
    INSTANCE;
    public void sayHello() {
        System.out.println("Hello World!");
    }
}

public class Test {
    public static void main(String[] args) {
      Singleton.INSTANCE.sayHello();
    }
}

枚举多例

 
public enum  Singleton {
    TOM("tom"),
    JACK("jack");
    private Singleton(String name){
        this.name = name ;
    };
    private String name;

    public void sayHello() {
        System.out.println("My name is "+this.name);
    }
}

public class Test {
    public static void main(String[] args) {
      Singleton.TOM.sayHello();
      Singleton.JACK.sayHello();
    }
}

小结

枚举方法一出,前面的方法都黯然失色了,看过刘慈欣《球状闪电》有一段话让我印象深刻,张彬花了一辈子时间用了很复杂的数据模型也没能解开球状闪电之谜,它怎么也没想到最终只是个电子而已。计算机系统如此复杂,最终执行的只是0和1,林将军说道:之所以失败,不是想得不够复杂,而是想得不够简单,感觉用枚举写单例就是对这句说最好的诠释。这种写法也是本文中唯一一种可以防止反射破坏单例的写法,Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。序列化的时候只将INSTANCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。

优点

  • 简洁、优雅
  • 可防止反射破坏单例

缺点

  • 有些人不知道这种写法

5. hutoll单例池

 
public class Dog {
    private Dog(){    }
    public void say() { System.out.println("汪汪"); }
}

public static void main(String[] args) {
        Dog dog = Singleton.get(Dog.class);
    }
}

Singleton官方文档

小结

hutool 的Singleton类就是通过一个ConcurrentHashMap将实列化的对象存入其中,如果Map中不存在对象就通过反射创建一个对象,确保Map中只有一个对象。

 
public final class Singleton {
   private static final ConcurrentHashMap<String, Object> POOL = new ConcurrentHashMap<>();
   private Singleton() {
   }
    /**
     * 获得指定类的单例对象<br>
     * 对象存在于池中返回,否则创建,每次调用此方法获得的对象为同一个对象<br>
     * 注意:单例针对的是类和参数,也就是说只有类、参数一致才会返回同一个对象
     *
     * @param <T>    单例对象类型
     * @param clazz  类
     * @param params 构造方法参数
     * @return 单例对象
     */
    public static <T> T get(Class<T> clazz, Object... params) {
       Assert.notNull(clazz, "Class must be not null !");
       final String key = buildKey(clazz.getName(), params);
       return get(key, () -> ReflectUtil.newInstance(clazz, params));
    }

  public static <T> T get(String key, Func0<T> supplier) {
      Object value = POOL.get(key);
      if(null == value){
            POOL.putIfAbsent(key, supplier.callWithRuntimeException());
            value = POOL.get(key);
      }
       return (T) value;
    }
}

值得注意的是虽然我们将Dog构造函数设置成了private,无法new Dog()但是还是可以通过反射来创建一个实例了,反射是会破环单例的,代码如下:

 
public static void main(String[] args) {
    Dog dog=  ReflectUtil.newInstance(Dog.class);
    System.out.println(dog == Singleton.get(Dog.class));   //false
}

优点

  • 不需要自己写任务代码,使用别人的轮子就简单多了

缺点

  • 需要引入第三方包

6. Spring @Component、@Service...

相信如今大部分项目都会用到Spring框架,都用到过了@Component、@Service、@Repository、@Controller,当加到这些注解后默认都是单例了。

@Component
public class Singleton {
    private Integer id = 0;

    private Singleton() {
    }

    public Integer sayHello() {
        System.out.println(String.format("id is %d", id++));
        return id;
    }
}


@RestController
public class TestController  {
    @Resource
    private Singleton singleton;
    @GetMapping("/test")
    public ResponseEntity<Integer> test() {
        return  ResponseEntity.ok(singleton.sayHello());
    }
}

上面的Demo中当我们每次请求 /test接口时,Singleton中的成员变量id都会+1,这也说明每次请求singleton是同一个对象了。但是当我们在Singleton和TestController上增加@Scope("prototype")情况就变了。

 
@Component
@Scope("prototype")
public class Singleton {
}

@RestController
@Scope("prototype")
public class TestController  {
}

 

我们可以看出,每次请求id都是1,说明不是单例了,需要指出的是只在Singleton添加@Scope("prototype")是不够的,因为RstController本身也是单例,所以要在二者上都加上@Scope("prototype")才能让Singleton变成单例。

小结

使用Spring的单例非常简单,增加一个注解就可以了,不过需要注意的是加上@Scope("prototype")会让它变成多例了,Bean也不会被Spring托管了,只是每次帮我们通过代码new了一个对象,具体原理可以参考 Spring的@Scope注解 prototype

优点

  • 此时无声,胜有声,不知不觉中就使用了单例

缺点

  • 隐藏了很多细节,不知道细节可能引起大问题

总结

单例的写法有很多,可能还有我不知道的写法,每种都有其优缺点,适用场景也不同,所以需要根据实际情况选择具体的方法。多知道一种方法,可能在下次遇到特定场景时会给你多一种选择,这种选择可能会比饿汉模式、懒汉模式更简洁、更优雅。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/459515.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 热题 100 | 回溯(二)

目录 1 39. 组合总和 2 22. 括号生成 3 79. 单词搜索 菜鸟做题&#xff0c;语言是 C&#xff0c;感冒快好版 关于对回溯算法的理解请参照我的上一篇博客&#xff1b; 在之后的博客中&#xff0c;我将只分析回溯算法中的 for 循环。 1 39. 组合总和 题眼&#xff1a;c…

Vue.js+SpringBoot开发个人健康管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 健康档案模块2.2 体检档案模块2.3 健康咨询模块 三、系统展示四、核心代码4.1 查询健康档案4.2 新增健康档案4.3 查询体检档案4.4 新增体检档案4.5 新增健康咨询 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpri…

c++入门你需要知道的知识点(上)

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 今日主菜&#xff1a;c入门 主厨&#xff1a;邪王真眼 所属专栏&#xff1a;c专栏 主厨的主页&#xff1a;Chef‘s blog 前言&#xff1a; 咱也是好久没有更…

【实战】VMware17虚拟机以及Centos7详细安装教程

文章目录 前言技术积累VMware虚拟机的安装下载VMware安装文件VMware安装步骤VMware配置密匙 虚拟机中安装centos7准备工作创建虚拟机步骤1 自定义安装步骤2 硬盘兼容性步骤3 安装客户机操作系统步骤4 选择客户机操作系统步骤5 命名虚拟机步骤6 处理器配置步骤7 设置虚拟机内存步…

Django之Cookie

Django之Cookie 目录 Django之Cookie介绍Django操作Cookie设置Cookie浏览器查看Cookie 获取Cookie设置超时Cookie注销Cookie 模拟登录验证登录验证装饰器登录验证装饰器-升级版 介绍 当我们上网使用社交媒体或者购物时&#xff0c;浏览器需要通过一种方式来记住我们。想象一下…

构造函数、原型、instanceof运算符

通过构造函数创建对象 构造函数是学习面向对象的基础 任何函数都有原型对象 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.…

Linux--基本知识入门

一.几个基本知识 终端: CtrlAltT 或者桌面/文件夹右键,打开终端切换为管理员: sudo su 退出:exit查看内核版本号: uname -a内核版本号含义: 5 代表主版本号;13代表次版本号;0代表修订版本号;30代表修订版本的第几次微调;数字越大表示内核越新. 二.目录…

ADC架构I:Flash转换器

目录 简介 量化噪声模型 量化噪声模型 量化噪声与输入信号之间的相关性容易令人误解 SNR、处理增益和FFT噪底的关系 简介 接触ADC或DAC时您一定会碰到这个经常被引用的公式&#xff0c;用于计算转换器理论信噪比 (SNR)。与其盲目地相信表象&#xff0c;不如从根本上了解其…

单目测距+姿态识别+yolov8界面+车辆行人跟踪计数

yolov5单目测距速度测量目标跟踪&#xff08;算法介绍和代码&#xff09; 1.单目测距实现方法 在目标检测的基础上&#xff0c;我们可以通过计算物体在图像中的像素大小来估计其距离。具体方法是&#xff0c;首先确定某个物体的实际尺寸&#xff0c;然后根据该物体在图像中的像…

Linux编译器gcc/g++的功能与使用

一、程序的生成 首先&#xff0c;我们知道程序的编译分为四步&#xff1a; 1、预处理 2、编译 3、汇编 4、链接 1.1预处理 预处理功能主要包括头文件展开、宏定义、文件包含、条件编译、去注释等。 所谓的头文件展开就是在预处理时候&#xff0c;将头文件内容拷贝至源文…

【优选算法】专题1 -- 双指针 -- 移动零

前言: &#x1f4da;为了提高算法思维&#xff0c;我会时常更新这个优选算法的系列&#xff0c;这个专题是关于双指针的练习 &#x1f3af;个人主页&#xff1a;Dream_Chaser&#xff5e;-CSDN博客 一.移动零&#xff08;easy&#xff09; 描述&#xff1a; 「数组分两块」是⾮…

构建部署_Docker常用命令

构建部署_Docker常见命令 启动命令镜像命令容器命令 启动命令 启动docker&#xff1a;systemctl start docker 停止docker&#xff1a;systemctl stop docker 重启docker&#xff1a;systemctl restart docker 查看docker状态&#xff1a;systemctl status docker 开机启动&…

Netty网络编程(一)

Netty网络编程&#xff08;一&#xff09; 如何进行网络通信 Socket通信是进程通讯的一种方式&#xff0c;通过调用这个网络库的一些API函数可以实现分布在不同主机的相关进程之间的数据交换 网络编程的基本流程是什么&#xff1f; 服务端先创建socket套接字&#xff0c;然后用…

HarmonyOS 非线性容器特性及使用场景

非线性容器实现能快速查找的数据结构&#xff0c;其底层通过 hash 或者红黑树实现&#xff0c;包括 HashMap、HashSet、TreeMap、TreeSet、LightWeightMap、LightWeightSet、PlainArray 七种。非线性容器中的 key 及 value 的类型均满足 ECMA 标准。 HashMap HashMap 可用来存…

L2-002 链表去重(Python)

给定一个带整数键值的链表 L&#xff0c;你需要把其中绝对值重复的键值结点删掉。即对每个键值 K&#xff0c;只有第一个绝对值等于 K 的结点被保留。同时&#xff0c;所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15&#xff0c;你需要输出去重后…

18 OpenCV霍夫变换检测直线

文章目录 HoughLines 算子HoughLinesP 算子示例 HoughLines 算子 cv::HoughLines( InputArray src, // 输入图像&#xff0c;必须8-bit的灰度图像 OutputArray lines, // 输出的极坐标来表示直线 double rho, // 生成极坐标时候的像素扫描步长 double theta, //生成极坐标时候…

干货|超实用的PMP学习资料

所有PMP备考笔记资料&#xff0c;文末获取&#xff01; 在通过PMP考试之后&#xff0c;我搜集整理了一些适合零基础入门的项目管理资料&#xff0c;想学习PMP的同学可以自取使用哦&#xff01; 有相关工作经验&#xff08;项目经理/产品经理/技术岗&#xff09; 有相关工作经…

解决ubuntu 22.04新内核6.5.0-15无法编译NVIDIA显卡驱动

这里的新内核应该包括6.5.*系列的 文章目录 遇到的问题&#xff1a; 遇到的问题&#xff1a; 今天我在安装NVIDIA显卡驱动发现了一个问题&#xff0c;主要日志如下所示&#xff1a; make[3]: *** [scripts/Makefile.build:251: /tmp/selfgz1310041/NVIDIA-Linux-x86_64-550.5…

【研发日记】Matlab/Simulink技能解锁(一)——在Simulink编辑窗口Debug

文章目录 前言 时间阈值断点 信号阈值断点 周期步进 Signal Value Lable Data Inspector 分析和应用 总结 前言 近期在一些研发项目中使用Matlab/Simulink时&#xff0c;遇到了挺多费时费力的事情。所以利用晚上和周末时间&#xff0c;在这些方面深入研究了一下&#x…

网站被挂马劫持的解决办法

首先&#xff0c;应该检查网站的DNS记录&#xff0c;以确定是否有人修改了DNS记录。如果发现有人修改了DNS记录&#xff0c;应该立即更改DNS记录&#xff0c;以恢复网站的正常访问。此外&#xff0c;应该检查网站的源代码&#xff0c;以确定是否有人植入了恶意代码。如果发现有…
最新文章