spring IOC bean为什么默认是单例的

首先解释一下什么是单例 bean?

单例的意思就是说在 Spring IoC 容器中只会存在一个 bean 的实例,无论一次调用还是多次调用,始终指向的都是同一个 bean 对象

用代码来解释单例 bean

public class UserService {public void sayHello() {System.out.println("hello");}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!-- scope 属性就是用来设置 bean 的作用域的,不配置的话默认就是单例,这里显示配置了 singleton --><bean id="userService" class="com.fyl.springboot.bean.singleton.UserService" scope="singleton"/></beans>
public class Demo {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans-singleton.xml");UserService service = context.getBean(UserService.class);UserService service1 = context.getBean(UserService.class);System.out.println(service == service1);}
}

运行 main 方法最后会输出:true,这就很明显的说明了无论多少次调用 getBean 方法,最终得到的都是同一个实例。

把上面 xml 文件的配置修改一下,修改为:

<!-- scope 的值改为了 prototype,表示每次请求都会创建一个新的 bean -->
<bean id="userService" class="com.fyl.springboot.bean.singleton.UserService" scope="prototype"/>

然后再次运行 main 方法,结果输出:false,说明两次调用 getBean 方法,得到的不是同一个实例。


了解了什么是单例 bean 之后,我们继续来说说单例 bean 的线程安全问题

为什么会存在线程安全问题呢?

因为对于单实例来说,所有线程都共享同一个 bean 实例,自然就会发生资源的争抢。

用代码来说明线程不安全的现象

public class ThreadUnSafe {public int i;public void add() {i++;}public void sub() {i--;}public int getValue() {return i;}
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><bean id="threadUnSafe" class="com.fyl.springboot.bean.singleton.ThreadUnSafe" scope="singleton"/></beans>
public class Demo {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans-singleton.xml");for (int j = 0; j < 10; j++) {new Thread(() -> {ThreadUnSafe service = context.getBean(ThreadUnSafe.class);for (int i = 0; i < 1000; i++) {service.add();}for (int i = 0; i < 1000; i++) {service.sub();}System.out.println(service.getValue());}).start();}}
}

上面的代码中,创建了 10 个线程来获取 ThreadUnSafe 实例,并且循环 1000 次加法,循环 1000 次减法,并把最后的结果打印出来。理想的情况是每个线程打印出来的结果都是 0

先看一下运行结果:

2073
1736
1080
1060
221
49
50
-231
-231
-231

从结果可以看出,运行结果都不是 0,这明显的是线程不安全啊!

为什么会出现这种情况?

因为 10 个线程获取的 ThreadUnSafe 实例都是同一个,并且 10 个线程都对同一个资源 i 发生了争抢,所以才会导致线程安全问题的发生。

现在把 xml 文件中的配置做一下更改:scope 的值改为 prototype

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!-- scope 的值改为 prototype --><bean id="threadUnSafe" class="com.fyl.springboot.bean.singleton.ThreadUnSafe" scope="prototype"/></beans>

然后再次运行 main 方法,发现无论运行多少次,最后的结果都是 0,是线程安全的!

因为 prototype 作用域下,每次获取的 ThreadUnSafe 实例都不是同一个,所以自然不会有线程安全的问题。

如果单例 bean 是一个无状态的 bean,还会有线程安全问题吗?

不会,无状态 bean 没有实例对象,不能保存数据,是不变类,是线程安全的。

public class ThreadSafe {public void getValue() {int val = 0;for (int i = 0; i < 1000; i++) {val++;}for (int i = 0; i < 1000; i++) {val--;}System.out.println(val);}
}
public class Demo {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans-singleton.xml");for (int i = 0; i < 10; i++) {new Thread(() -> {ThreadSafe service = context.getBean(ThreadSafe.class);service.getValue();}).start();}}
}

运行结果为 0

事实证明,无状态的 bean 是线程安全的。(无状态 bean 应该是这个意思,如有不对的地方,还望指出)

那么针对单例 bean,而且是有状态的 bean,应该如何保证线程安全呢?

那有人肯定会说了:既然是线程安全问题,那就加锁呗!

毫无疑问加锁确实可以,但是加锁多多少少有点性能上的下降

加锁代码如下所示:

public class Demo {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans-singleton.xml");for (int j = 0; j < 10; j++) {new Thread(() -> {ThreadUnSafe service = context.getBean(ThreadUnSafe.class);synchronized (service) {for (int i = 0; i < 1000; i++) {service.add();}for (int i = 0; i < 1000; i++) {service.sub();}System.out.println(service.getValue());}}).start();}}
}

还有一种方法是使用 ThreadLocal

ThreadLocal 简单的说就是在自己线程内创建一个变量的副本,那么线程操作的自然也就是自己线程内的资源了,也就规避了线程安全问题。但是却带来了空间上的开销。

使用方法如下:

public class ThreadUnSafe {ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public void add() {Integer i = threadLocal.get();if (i == null) {i = 0;}i++;threadLocal.set(i);}public void sub() {Integer i = threadLocal.get();i--;threadLocal.set(i);}public Integer getValue() {return threadLocal.get();}
}

public class Demo {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans-singleton.xml");for (int j = 0; j < 10; j++) {new Thread(() -> {ThreadUnSafe service = context.getBean(ThreadUnSafe.class);for (int i = 0; i < 1000; i++) {service.add();}for (int i = 0; i < 1000; i++) {service.sub();}System.out.println(service.getValue());}).start();}}
}

使用 ThreadLocal 即使不加锁也保证了输出的结果都是 0

加锁和使用 ThreadLocal 各有各的特点

  • 加锁是以时间换空间
  • ThreadLocal 是以空间换时间

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

交叉编译工具链makefile

linux系统默认搜索头文件地址&#xff1a;/usr/include/文件夹&#xff1b; Windows系统默认搜索头文件地址&#xff1a;不同软件好像可以设置不同的地址&#xff1b;例如visual studio好像可以设置附加包含目录&#xff0c;包含目录等 Linux系统库文件路径&#xff1a;/lib文…

通过生成模拟释放无限数据以实现机器人自动化学习

该工作推出RoboGen&#xff0c;这是一种生成机器人代理&#xff0c;可以通过生成模拟自动大规模学习各种机器人技能。 RoboGen 利用基础模型和生成模型的最新进展。该工作不直接使用或调整这些模型来产生策略或低级动作&#xff0c;而是提倡一种生成方案&#xff0c;该方案使用…

命运天注定?

罗翔老师经常说&#xff1a;人这一生&#xff0c;能自己决定的也许只有5&#xff05;&#xff0c;有95%是你决定不了的。 不是说事在人为&#xff0c;人定胜天吗&#xff1f; 哪吒也在电影的高潮喊出了&#xff1a;我命由我不由天。 听上去很热血&#xff0c;但实际咱们每个…

Java泛型:详解使用技巧及举例说明

Java泛型&#xff1a;详解使用技巧及举例说明 1. 引言 Java泛型是一项强大的编程概念&#xff0c;它允许我们编写通用的代码&#xff0c;在编写代码时不需要预先指定具体的数据类型。泛型的引入解决了在传统的编程中需要频繁进行类型转换的问题&#xff0c;提高了代码的安全性…

simulink MATLABFunction模块中实时函数调用函数的使用

样例 function Predyy matlabceshi(input, Time_s) input1 input; Time_s1 Time_s; Predyy ee(input1) mm(Time_s1); end 上面是主要部分&#xff0c;下面是被调用部分 function A ee(input1) A input1 * 100; end function B mm(Time_s1) B Time_s1 * 100; end 模型…

算法竞赛---反悔贪心

反悔贪心 Work Scheduling G 什么是返回贪心呢&#xff0c;就是先选择&#xff0c;遇到更好的之后在反悔选择更好的&#xff0c;这是符合贪心的逻辑的。 #include <bits/stdc.h> // https://www.luogu.com.cn/problem/P2949 using namespace std; struct node {int d,…

Linux(ubuntu)利用ffmpeg+qt设计rtsp_rtmp流媒体播放器(完全从0开始搭建环境进行开发)

一、前言 从0开始搭建Linux下Qt、ffmpeg开发环境。 从安装虚拟机开始、安装Linux(Ubuntu)系统、安装Qt开发环境、编译ffmpeg源码、配置ffmpeg环境、编写ffmpeg项目代码、完成项目开发。 完全从0开始搭建环境进行开发 完全从0开始搭建环境进行开发 完全从0开始搭建环境进行开…

公务员国考省考小白需知

文章目录&#xff1a; 一&#xff1a;分类 1.国考 2.省考 二&#xff1a;必备途径 1.相关网站 1.1 官网 1.1.1 必须知道的 1.1.2 比较好用的 1.1.3 事业单位的 1.2 机构 ​​1.3 时事 ​​1.4 资源 1.5 题库 1.6 真题 ​2.相关公主号 3.应用 4.群聊如何找 三…

笙默考试管理系统-MyExamTest----codemirror(53)

笙默考试管理系统-MyExamTest----codemirror&#xff08;53&#xff09; 目录 笙默考试管理系统-MyExamTest----codemirror&#xff08;51&#xff09; 一、 笙默考试管理系统-MyExamTest----codemirror 二、 笙默考试管理系统-MyExamTest----codemirror 三、 笙默考试…

【TwinCAT学习笔记 1】TwinCAT开发环境搭建

写在前面 作为技术开发人员&#xff0c;开启任何一项开发工作之前&#xff0c;首先都要搭建好开发环境&#xff0c;所谓磨刀不误砍材工&#xff0c;一定要有耐心&#xff0c;一次不行卸载再装。我曾遇到过一个学生&#xff0c;仅搭建环境就用了两周&#xff0c;这个过程也是一…

ATM的转账

【 1 】明确我们要实现的功能 # 用户功能菜单 # 1.注册 # 2.登陆 # 3.取款 # 4.转账 # 5.充值余额 # 6.查看流水 # 7.查看银行信息(查看自己…

基于Redis在定时任务里判断其他定时任务是否已经正常执行完的方案

执行的定时任务是基于其他定时任务计算得到的结果基础上做操作的&#xff0c;那么如何来确定其他存在数据依赖的定时任务已经执行完成呢&#xff1f; 在分布式环境里&#xff0c;可通过集群的redis来解决这个问题&#xff1a; 即&#xff0c;在跑批任务开始时&#xff0c;将任…

SSD数据在写入NAND之前为何要随机化?-part2

接part1介绍&#xff1a; 如何达到这个目的&#xff1f;业内常用的是对写入数据的数据进行随机化处理&#xff0c;这部分主要在SSD控制器中通过硬件实现。 上图b/c&#xff1a;在控制器芯片通过硬件方式实现随机化的读写流程&#xff0c;这个也是业内通常做法。随机化处理是在写…

【K8S in Action】服务:让客户端发现pod 并与之通信(1)

服务是一种为一组功能相同的 pod 提供单一不变的接入点的资源。当服务存在时&#xff0c;它的 IP 地址和端口不会改变。 客户端通过 IP 地址和端口号建立连接&#xff0c; 这些连接会被路由到提供该服务的任意一个 pod 上。 pod 是短暂&#xff0c;会删除增加&#xff0c;调度…

Android 13 Settings蓝牙列表卡顿问题排查及优化过程

一.背景 此问题是蓝牙列表界面息屏后再点击亮屏蓝牙界面卡住,划不动也不能返回,在人多的时候(附近开启的蓝牙设备过多的时候)会卡住大概四五秒才能滑动. 优化前效果见资源: 二.查找耗时点 根据Android Studio的Profiler工具进行排查,查找主线程时间线比较长的方法,如下:…

IDEA远程调试与JDWP调试端口RCE漏洞

文章目录 前言Docker远程调试Java调试原理远程调试实践 JDWP端口RCE调试端口探测调试端口利用 总结 前言 在对一些 Java CVE 漏洞的调试分析过程中&#xff0c;少不了需要搭建漏洞环境的场景&#xff0c;但是本地 IDEA 搭建的话既麻烦&#xff08;通过 pom.xml 导入各种漏洞组…

面向对象编程教程

面向对象编程是一种基于对象的编程范型&#xff0c;它将程序中的数据和操作数据的方法看作一个整体&#xff0c;通过封装、继承和多态等机制来实现代码的复用和可扩展性。面向对象编程也是现代软件开发的主流编程范式之一&#xff0c;广泛应用于各种编程语言中&#xff0c;如C、…

Zookeeper系统性学习-应用场景以及单机、集群安装

Zookeeper 是什么&#xff1f; Zookeeper 为分布式应用提供高效且可靠的分布式协调服务&#xff0c;提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面&#xff0c;ZooKeeper 并没有直接采用 Paxos 算法&#xff0c;而是采用了名为 …

Android Studio Gradle下载慢解决方法

Android Studio Gradle下载慢解决方法 最近在练习模型部署&#xff0c;主要是在手机端部署&#xff0c;所以使用到了Android Studio&#xff0c;但是在创建项目的时候&#xff0c;一致在下载gradle&#xff0c;而且网速还很慢&#xff0c;不对&#xff0c;是极慢哪种&#xff0…

MQTT发布、订阅和取消订阅

在本文中&#xff0c;我们将深入了解MQTT发布、订阅和取消订阅相关的内容。如果你刚接触发布/订阅模型&#xff0c;建议阅读本专栏之前的文章。 什么是MQTT发布消息 在MQTT中&#xff0c;一个客户端连接到代理&#xff08;broker&#xff09;之后可以立即发布消息。这些消息依…