SpringMVC:如何保證Controller的並發安全


SpringMVC中的Controller是單例還是多例?很多同學可能會想當然認為Controller是多例,其實不然。

對於一個瀏覽器請求,tomcat會指定一個處理線程,或是在線程池中選取空閑的,或者新建一個線程。在Tomcat容器中,每個servlet是單例的。

在SpringMVC中,Controller 默認也是單例。 采用單例模式的最大好處,就是可以在高並發場景下極大地節省內存資源,提高服務抗壓能力。

單例模式容易出現的問題是:在Controller中定義的實例變量,在多個請求並發時會出現競爭訪問,Controller中的實例變量不是線程安全的。

正因為Controller默認是單例,所以不是線程安全的。如果用SpringMVC 的 Controller時,盡量不在 Controller中使用實例變量,否則會出現線程不安全性的情況,導致數據邏輯混亂。

舉一個簡單的例子,在一個Controller中定義一個非靜態成員變量 num 。通過Controller成員方法來對 num 增加。

@Controller
public class TestController {
    private int num = 0;

    @RequestMapping("/addNum")
    public void addNum() {
        System.out.println(++num);
    }
}

在本地運行后:

  • 首先訪問 http:// localhost:8080 / addNum,得到的答案是1;
  • 再次訪問 http:// localhost:8080 / addNum,得到的答案是 2。
  • 兩次訪問得到的結果不同,num已經被修改,並不是我們希望的結果,接口的冪等性被破壞。

從這個例子可以看出,所有的請求訪問同一個Controller實例,Controller的私有成員變量就是線程共用的。某個請求對應的線程如果修改了這個變量,那么在別的請求中也可以讀到這個變量修改后的的值。

Controller並發安全的解決辦法

如果要保證Controller的線程安全,有以下解決辦法:

  • 盡量不要在 Controller 中定義成員變量 ;
  • 如果必須要定義一個非靜態成員變量,那么可以通過注解 @Scope(“prototype”) ,將Controller設置為多例模式。
@Controller
@Scope(value="prototype")
public class TestController {
    private int num = 0;

    @RequestMapping("/addNum")
    public void addNum() {
        System.out.println(++num);
    }
}

Scope屬性是用來聲明IOC容器中的對象(Bean )允許存在的限定場景,或者說是對象的存活空間。在對象進入相應的使用場景之前,IOC容器會生成並裝配這些對象;當該對象不再處於這些使用場景的限定時,容器通常會銷毀這些對象。

Controller也是一個Bean,默認的 Scope 屬性為Singleton ,也就是單例模式。如果Bean的 Scope 屬性設置為 prototype 的話,容器在接受到該類型對象的請求時,每次都會重新生成一個新的對象給請求方。

  • Controller 中使用 ThreadLocal 變量。 每一個線程都有一個變量的副本。
public class TestController {
    private int num = 0;
    private final ThreadLocal <Integer> uniqueNum =
             new ThreadLocal <Integer> () {
                 @Override protected Integer initialValue() {
                     return num;
                 }
             };

    @RequestMapping("/addNum")
    public void addNum() {
        int unum = uniqueNum.get();
       uniqueNum.set(++unum);
       System.out.println(uniqueNum.get());
    }
}

以上代碼運行以后,每次請求 http:// localhost:8080 / addNum , 得到的結果都是1。

更嚴格的做法是用AtomicInteger類型定義成員變量,對於成員變量的操作使用AtomicInteger的自增方法完成。

總的來說,還是盡量不要在 Controller 中定義成員變量為好。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM