淺析Spring IOC bean為什么默認(rèn)是單例
首先解釋一下什么是單例 bean?
單例的意思就是說(shuō)在 Spring IoC 容器中只會(huì)存在一個(gè) bean 的實(shí)例,無(wú)論一次調(diào)用還是多次調(diào)用,始終指向的都是同一個(gè) bean 對(duì)象
用代碼來(lái)解釋單例 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.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- scope 屬性就是用來(lái)設(shè)置 bean 的作用域的,不配置的話默認(rèn)就是單例,這里顯示配置了 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);
}
}運(yùn)行 main 方法最后會(huì)輸出:true,這就很明顯的說(shuō)明了無(wú)論多少次調(diào)用 getBean 方法,最終得到的都是同一個(gè)實(shí)例。
把上面 xml 文件的配置修改一下,修改為:
<!-- scope 的值改為了 prototype,表示每次請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的 bean --> <bean id="userService" class="com.fyl.springboot.bean.singleton.UserService" scope="prototype"/>
然后再次運(yùn)行 main 方法,結(jié)果輸出:false,說(shuō)明兩次調(diào)用 getBean 方法,得到的不是同一個(gè)實(shí)例。
了解了什么是單例 bean 之后,我們繼續(xù)來(lái)說(shuō)說(shuō)單例 bean 的線程安全問(wèn)題
為什么會(huì)存在線程安全問(wèn)題呢?
因?yàn)閷?duì)于單實(shí)例來(lái)說(shuō),所有線程都共享同一個(gè) bean 實(shí)例,自然就會(huì)發(fā)生資源的爭(zhēng)搶。
用代碼來(lái)說(shuō)明線程不安全的現(xiàn)象
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.xsd
http://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();
}
}
}上面的代碼中,創(chuàng)建了 10 個(gè)線程來(lái)獲取 ThreadUnSafe 實(shí)例,并且循環(huán) 1000 次加法,循環(huán) 1000 次減法,并把最后的結(jié)果打印出來(lái)。理想的情況是每個(gè)線程打印出來(lái)的結(jié)果都是 0
先看一下運(yùn)行結(jié)果:
2073
1736
1080
1060
221
49
50
-231
-231
-231
從結(jié)果可以看出,運(yùn)行結(jié)果都不是 0,這明顯的是線程不安全??!
為什么會(huì)出現(xiàn)這種情況?
因?yàn)?10 個(gè)線程獲取的 ThreadUnSafe 實(shí)例都是同一個(gè),并且 10 個(gè)線程都對(duì)同一個(gè)資源 i 發(fā)生了爭(zhēng)搶,所以才會(huì)導(dǎo)致線程安全問(wèn)題的發(fā)生。
現(xiàn)在把 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.xsd
http://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>然后再次運(yùn)行 main 方法,發(fā)現(xiàn)無(wú)論運(yùn)行多少次,最后的結(jié)果都是 0,是線程安全的!
因?yàn)?prototype 作用域下,每次獲取的 ThreadUnSafe 實(shí)例都不是同一個(gè),所以自然不會(huì)有線程安全的問(wèn)題。
如果單例 bean 是一個(gè)無(wú)狀態(tài)的 bean,還會(huì)有線程安全問(wèn)題嗎?
不會(huì),無(wú)狀態(tài) bean 沒(méi)有實(shí)例對(duì)象,不能保存數(shù)據(jù),是不變類,是線程安全的。
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();
}
}
}
運(yùn)行結(jié)果為 0
事實(shí)證明,無(wú)狀態(tài)的 bean 是線程安全的。(無(wú)狀態(tài) bean 應(yīng)該是這個(gè)意思,如有不對(duì)的地方,還望指出)
那么針對(duì)單例 bean,而且是有狀態(tài)的 bean,應(yīng)該如何保證線程安全呢?
那有人肯定會(huì)說(shuō)了:既然是線程安全問(wèn)題,那就加鎖唄!
毫無(wú)疑問(wèn)加鎖確實(shí)可以,但是加鎖多多少少有點(diǎn)性能上的下降
加鎖代碼如下所示:
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 簡(jiǎn)單的說(shuō)就是在自己線程內(nèi)創(chuàng)建一個(gè)變量的副本,那么線程操作的自然也就是自己線程內(nèi)的資源了,也就規(guī)避了線程安全問(wèn)題。但是卻帶來(lái)了空間上的開銷。
使用方法如下:
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 即使不加鎖也保證了輸出的結(jié)果都是 0
加鎖和使用 ThreadLocal 各有各的特點(diǎn)
- 加鎖是以時(shí)間換空間
- ThreadLocal 是以空間換時(shí)間
以上就是淺析Spring IOC bean為什么默認(rèn)是單例的詳細(xì)內(nèi)容,更多關(guān)于Spring IOC bean默認(rèn)單例的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Maven在Windows中的配置以及IDE中的項(xiàng)目創(chuàng)建(圖文教程)
這篇文章主要介紹了Maven在Windows中的配置以及IDE中的項(xiàng)目創(chuàng)建(圖文教程),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
詳解關(guān)于springboot-actuator監(jiān)控的401無(wú)權(quán)限訪問(wèn)
本篇文章主要介紹了詳解關(guān)于springboot-actuator監(jiān)控的401無(wú)權(quán)限訪問(wèn),非常具有實(shí)用價(jià)值,有興趣的可以了解一下2017-09-09
Java實(shí)戰(zhàn)之用Spring開發(fā)條形碼和驗(yàn)證碼
這篇文章主要介紹了Java實(shí)戰(zhàn)之用Spring開發(fā)條形碼和驗(yàn)證碼,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
java判斷用戶輸入的是否至少含有N位小數(shù)的實(shí)例
下面小編就為大家分享一篇java判斷用戶輸入的是否至少含有N位小數(shù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
使用IntelliJ IDEA調(diào)式Stream流的方法步驟
本文主要介紹了使用IntelliJ IDEA調(diào)式Stream流的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05

