天天看點

Spring中的Controller ,Service,Dao是不是線程安全的?

結論:不是線程安全的

spring容器中的bean是否線程安全,容器本身并沒有提供bean的線程安全政策,是以可以說spring容器中的bean本身不具備線程安全的特性,但是具體還是要結合具體scope的bean去研究。

spring 的 bean 作用域(scope)類型

singleton:單例,預設作用域。

prototype:原型,每次建立一個新對象。

request:請求,每次http請求建立一個新對象,适用于webapplicationcontext環境下。

session:會話,同一個會話共享一個執行個體,不同會話使用不用的執行個體。

global-session:全局會話,所有會話共享一個執行個體。

線程安全這個問題,要從單例與原型bean分别進行說明。

原型bean

對于原型bean,每次建立一個新對象,也就是線程之間并不存在bean共享,自然是不會有線程安全的問題。

單例bean

對于單例bean,所有線程都共享一個單例執行個體bean,是以是存在資源的競争。

如果單例bean,是一個無狀态bean,也就是線程中的操作不會對bean的成員執行查詢以外的操作,那麼這個單例bean是線程安全的。比如spring mvc 的 controller、service、dao等,這些bean大多是無狀态的,隻關注于方法本身。

spring中的bean預設是單例模式的,架構并沒有對bean進行多線程的封裝處理。

實際上大部分時間bean是無狀态的(比如dao) 是以說在某種程度上來說bean其實是安全的。

但是如果bean是有狀态的 那就需要開發人員自己來進行線程安全的保證,最簡單的辦法就是改變bean的作用域 把 "singleton"改為’‘protopyte’ 這樣每次請求bean就相當于是 new bean() 這樣就可以保證線程的安全了。

有狀态就是有資料存儲功能

無狀态就是不會儲存資料    controller、service和dao層本身并不是線程安全的,隻是如果隻是調用裡面的方法,而且多線程調用一個執行個體的方法,會在記憶體中複制變量,這是自己的線程的工作記憶體,是安全的。

想了解原理可以看看《深入了解jvm虛拟機》,2.2.2節:

java虛拟機棧是線程私有的,它的生命周期與線程相同。虛拟機棧描述的是java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊。

《java并發程式設計實戰》第3.2.2節:

局部變量的固有屬性之一就是封閉在執行線程中。它們位于執行線程的棧中,其他線程無法通路這個棧。

是以其實任何無狀态單例都是線程安全的。

spring的根本就是通過大量這種單例建構起系統,以事務腳本的方式提供服務。搜尋java知音公衆号,回複“後端面試”,送你一份java面試題寶典.pdf

答:預設配置下不是的。為啥呢?因為預設情況下@controller沒有加上@scope,沒有加@scope就是預設值singleton,單例的。意思就是系統隻會初始化一次controller容器,是以每次請求的都是同一個controller容器,當然是非線程安全的。舉個栗子:

在postman裡面發三次請求,結果如下:

在公衆号後端架構師背景回複“offer”,擷取算法面試題和答案。

說明他不是線程安全的。怎麼辦呢?可以給他加上上面說的@scope注解,如下:

這樣一來,每個請求都單獨建立一個controller容器,是以各個請求之間是線程安全的,三次請求結果:

加了@scope注解多的執行個體prototype是不是一定就是線程安全的呢?

看三次請求結果:

雖然每次都是單獨建立一個controller但是扛不住他變量本身是static的呀,是以說呢,即便是加上@scope注解也不一定能保證controller 100%的線程安全。是以是否線程安全在于怎樣去定義變量以及controller的配置。

搜尋java知音公衆号,回複“後端面試”,送你一份java面試題寶典.pdf

是以來個全乎一點的實驗,代碼如下:

補充controller以外的代碼:

config裡面自己定義的bean:user

我暫時能想到的定義變量的方法就這麼多了,三次http請求結果如下:

可以看到,在單例模式下controller中隻有用threadlocal封裝的變量是線程安全的。為什麼這樣說呢?我們可以看到3次請求結果裡面隻有threadlocal變量值每次都是從0+1=1的,其他的幾個都是累加的,而user對象呢,預設值是0,第二交取值的時候就已經是1了,關鍵他的hashcode是一樣的,說明每次請求調用的都是同一個user對象。

在公衆号後端架構師背景回複“java”,擷取java面試題和答案。

下面将testcontroller 上的@scope注解的屬性改一下改成多執行個體的:@scope(value = "prototype"),其他都不變,再次請求,結果如下:

分析這個結果發現,多執行個體模式下普通變量,取配置的變量還有threadlocal變量都是線程安全的,而靜态變量和user(看他的hashcode都是一樣的)對象中的變量都是非線程安全的。

也就是說盡管testcontroller 是每次請求的時候都初始化了一個對象,但是靜态變量始終是隻有一份的,而且這個注入的user對象也是隻有一份的。靜态變量隻有一份這是當然的咯,那麼有沒有辦法讓user對象可以每次都new一個新的呢?當然可以:

在config裡面給這個注入的bean加上一個相同的注解@scope(value = "prototype")就可以了,再來請求一下看看:

可以看到每次請求的user對象的hashcode都不是一樣的,每次指派前取user中的變量值也都是預設值0。

下面總結一下:

1、在@controller/@service等容器中,預設情況下,scope值是單例-singleton的,也是線程不安全的。

2、盡量不要在@controller/@service等容器中定義靜态變量,不論是單例(singleton)還是多執行個體(prototype)他都是線程不安全的。

3、預設注入的bean對象,在不設定scope的時候他也是線程不安全的。

4、一定要定義變量的話,用threadlocal來封裝,這個是線程安全的