1. 程式人生 > >單例模式下全域性變量出現的問題

單例模式下全域性變量出現的問題

先看下面小段程式碼,一個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正確時,可以確定不會出現併發問題。