先看下面小段代碼,一個controller,一個service。
controller.java代碼:
........
@Autowired
private XXXService xxxService;
........
@RequestMapping("/doXXX.do")
public void doXXX(){
.....
xxxService.saveXXX(String content,....);
.....
}
XXXService.java代碼:
private String content;
......
private void init(){//清空請求參數
content = null;
......
}
public boolean saveXXX(String content, ......){
this.init(content, ...);
this.content = content;
//業務邏輯處理
}
以上這段代碼在訪問量不構成並發時不會出現什么問題。 但當一個請求還未完成,另一個請求已經開始執行的情況下就會出現問題(並發): 第二個請求執行執行init()方法會將第一個請求的content變量設置為null或它本身的值,這樣數據就被篡改了。
編碼者這樣寫的目的是因為content等變量需要在多個方法中使用,而且變量很多,但又不想通過方法參數的方式來傳遞,故使用成員變量。
先看看為什么會出現這種情況。 由於系統采用springmvc框架,springmvc核心控制器DispatcherServlet 默認為每個controller生成單一實例來處理所有用戶請求,所以在這個單一實例的controller中,它的XXXService也是一個實例處理所有請求, 這樣XXXService的成員變量就被所有請求共享。這樣就會出現並發請求時變量內容被篡改的問題。
那么出現這種問題如何解決呢?
第一種方式: 既然是全局變量惹的禍,那就將全局變量都編程局部變量,通過方法參數來傳遞。
第二種方式: jdk提供了java.lang.ThreadLocal,它為多線程並發提供了新思路。 (當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本)
那么在什么地方使用ThreadLocal呢? 什么變量是請求公用的就將該變量托付給ThreadLocal來管理其線程副本, 所以我們在xxxService中使用它。
XXXService.java代碼:
private ThreadLocal<String> contentTL = new ThreadLocal<String>();
//private String content;使用contentTL代替content;
......
public boolean saveXXX(String content, ......){
this.contentTL.set(content);
//業務邏輯處理
//在各方法中使用content時候用this.contentTL.get()代替
}
此類並發篡改數據的問題,可以在開發工具中設置斷點調試的方式來模擬並發。即第一次請求運行到斷點時,查看content內容,並且不讓程序繼續往下運行,同時再發起一個請求,查看content內容。 如內容是第一次請求的內容,並且讓第一個請求跑完后,第二個請求到斷線處的content正確時,可以確定不會出現並發問題。