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.pswp.cn/news/213027.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/213027.shtml
英文地址,請注明出處:http://en.pswp.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;之后可以立即發布消息。這些消息依…