默認情況下,Spring Boot 中的 Bean 是非線程安全的。這是因為,默認情況下 Bean 的作用域是單例模式,那么此時,所有的請求都會共享同一個 Bean 實例,這意味著這個 Bean 實例,在多線程下可能被同時修改,那么此時它就會出現線程安全問題。
“
Bean 的作用域(Scope)指的是確定在應用程序中創建和管理 Bean 實例的范圍。也就是在 Spring 中,可以通過指定不同的作用域來控制 Bean 實例的生命周期和可見性。例如,單例模式就是所有線程可見并共享的,而原型模式則是每次請求都創建一個新的原型對象。
”
并不是,單例 Bean 分為以下兩種類型:
所以說:有狀態的單例 Bean 是非線程安全的,而無狀態的 Bean 是線程安全的。
“
但在程序中,只要有一種情況會出現線程安全問題,那么它的整體就是非線程安全的,所以總的來說,單例 Bean 還是非線程安全的。
”
無狀態的 Bean 指的是不存在成員變量,或只有查詢操作,沒有修改操作,它的實現示例代碼如下:
import org.springframework.stereotype.Service;@Servicepublic class StatelessService { public void doSomeTask() { // 執行任務 }}
有成員變量,并且存在對成員變量的修改操作,如下代碼所示:
import org.springframework.stereotype.Service;@Servicepublic class UserService { private int count = 0; public void incrementCount() { count++; // 非原子操作,并發存在線程安全問題 } public int getCount() { return count; }}
想要保證有狀態 Bean 的線程安全,可以從以下幾個方面來實現:
具體實現如下。
實現代碼如下:
import org.springframework.stereotype.Service;@Servicepublic class UserService { private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0); public void incrementCount() { count.set(count.get() + 1); } public int getCount() { return count.get(); }}
使用 ThreadLocal 需要注意一個問題,在用完之后記得調用 ThreadLocal 的 remove 方法,不然會發生內存泄漏問題。
鎖機制中最簡單的是使用 synchronized 修飾方法,讓多線程執行此方法時排隊執行,這樣就不會有線程安全問題了,如下代碼所示:
import org.springframework.stereotype.Service;@Servicepublic class UserService { private int count = 0; public synchronized void incrementCount() { count++; // 非原子操作,并發存在線程安全問題 } public int getCount() { return count; }}
原型作用域通過 @Scope("prototype") 來設置,表示每次請求時都會生成一個新對象(也就沒有線程安全問題了),如下代碼所示:
import org.springframework.stereotype.Service;@Service@Scope("prototype")public class UserService { private int count = 0; public void incrementCount() { count++; // 非原子操作,并發存在線程安全問題 } public int getCount() { return count; }}
我們可以使用線程安全的容器,例如 AtomicInteger 來替代 int,從而保證線程安全,如下代碼所示:
import org.springframework.stereotype.Service;import java.util.concurrent.atomic.AtomicInteger;@Servicepublic class UserService { private AtomicInteger count = new AtomicInteger(0); public void incrementCount() { count.incrementAndGet(); } public int getCount() { return count.get(); }}
實際工作中,通常會根據具體的業務場景來選擇合適的線程安全方案,但是以上解決線程安全的方案中,ThreadLocal 和原型作用域會使用更多的資源,占用更多的空間來保證線程安全,所以在使用時通常不會作為最佳考慮方案。
而鎖機制和線程安全的容器通常會優先考慮,但需要注意的是 AtomicInteger 底層是樂觀鎖 CAS 實現的,因此它存在樂觀鎖的典型問題 ABA 問題(如果有狀態的 Bean 中既有 ++ 操作,又有 -- 操作時,可能會出現 ABA 問題),此時就要使用鎖機制,或 AtomicStampedReference 來解決 ABA 問題了。
單例模式的 Bean 并不一定都是非線程安全的,其中有狀態的 Bean 是存在線程安全問題的。實際工作中通常會使用鎖機制(synchronized 或 ReentrantLock)或線程安全的容器來解決 Bean 的線程安全問題,但具體使用哪種方案,還要結合具體業務場景來定。
本文鏈接:http://www.www897cc.com/showinfo-26-60980-0.html面試官:單例Bean一定不安全嗎?實際工作中如何處理此問題?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: Go語言常見錯誤—Any 沒傳遞任何信息