1. 程式人生 > >Spring MVC Controller的執行緒安全

Spring MVC Controller的執行緒安全

問題:

  • spring mvc controller執行緒安全嗎?
  • 引申servlet及struts1/2的Action執行緒安全嗎?

知識點:

  • 例項變數和類變數(靜態變數)
  • 類&單例項&多例項(如何知道一個類有多少個例項)
  • 執行緒名稱&執行緒安全
  • spring mvc controller單例項OR多例項 web容器啟動時區別
  • synchronized使用

實驗:

  • 實驗方法: 
    通過是否使用@Scope(“prototype”)註解來控制controller單&多例項; 
    通過類變數進行例項計數跟蹤; 
    通過例項變數驗證單&多例項執行緒安全; 
    通過synchronized更直觀體驗多執行緒併發訪問;

  • 實驗程式碼: 
    伺服器程式碼:

package com.test;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**  
 *
 * @author : Ares.yi
 * @createTime : 2016-04-10 上午11:13:42 
 * @version : 1.0 
 * @description : 
 *
 */
@Controller
@RequestMapping("/hello")
@Scope("prototype")
public class TestController {

    /**類變數 例項個數計數器*/
    private static int instanceCount = 0;

    /**例項變數*/
    private /**volatile*/ String instanceVar;

    /**程式碼塊鎖,替換this等變數synchronized方法Or程式碼塊同步*/
    private /**static*/ byte[] lock = new byte[0];

    public TestController() {
        instanceCount++;
        log("\tCreate 'TestController' new instance->"+instanceCount);
    }


    /**
     * 測試
     * 
     * @param reqOrder:請求順序
     * @param name:請求名稱(客戶端使用多執行緒訪問時可以使用執行緒名稱)
     * @param sleepMillis:通過使用者請求時隨機傳遞一個時間值來模擬伺服器處理不同使用者時所需不同耗時
     * @return
     *
     * @author : Ares.yi
     * @createTime : 2016年04月10日 下午3:25:41
     */
    @RequestMapping(value="test",produces = "application/json; charset=UTF-8")
    @ResponseBody
    public  String test(int reqOrder,String name,long sleepMillis){

        long now = System.currentTimeMillis();

        simulateUseTimeConsuming(sleepMillis);  

        long end = System.currentTimeMillis();

        /**注意比較單&多例項時,多執行緒併發訪問影響*/
        instanceVar = name;

        String s = " reqOrder:"+reqOrder+",instance:"+instanceCount+",instanceVar:"+instanceVar+"-->{sleep:"+sleepMillis+"ms,useTime:"+(end-now)+"ms,name:"+name+"}";

        log(s);
        return s;
    }

    /**
     * 耗時模擬
     * 注意:單&多例項在多執行緒訪問時,體驗synchronized對使用者併發訪問的影響
     * @param sleepMillis
     *
     * @author : Ares.yi
     * @createTime : 2016年04月10日 下午2:46:54
     */
    private /**synchronized*/ void simulateUseTimeConsuming(long sleepMillis){
        try {
            Thread.sleep(sleepMillis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private <T> void log(T t){
        System.out.println("\t"+Thread.currentThread().getName()+"==>"+t);
    }

}

客戶端模擬程式碼(wget或瀏覽器手動請求一樣):

package com.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.xxx.common.tools.CommonUtils;
import com.xxx.common.tools.web.WebUtils;

/**  
 *
 * @author : Ares.yi
 * @createTime : 2016年04月10日 下午3:25:41
 * @version : 1.0 
 * @description : 
 *
 */
public class Test {

    /**
     * @param args
     *
     * @author : Ares.yi
     * @createTime :2016年04月10日 下午4:25:41
     */
    public static void main(String[] args) {
        /**m個執行緒,模擬n個使用者併發請求 (嘗試著分別調整為不同的值)*/
        int m = 10 , n = 100;

        ExecutorService pool = Executors.newFixedThreadPool(m);
        for(int i = 0 ; i < n ;i++){
            final int order = i;

            pool.execute(new Runnable() {

                @Override
                public void run() {
                    /**隨機0.3~2秒   調整更長時間直觀體驗併發訪問直接的影響*/
                    long millis = CommonUtils.getRandomNumber(300, 2000);

                    try {
                        String threadName = Thread.currentThread().getName();//獲取當前執行緒名稱

                        String url = "http://localhost:70/springmvc-example/hello/test?reqOrder="
                                +order+"&name="+threadName
                                +"&sleepMillis="+millis;

                        System.out.println(threadName+"==>"+WebUtils.fetchContent(url));
                    } catch (Exception e) {
                        //e.printStackTrace();
                        System.err.println(order+"==>"+e.getMessage()+" force sleep:"+millis);
                    }
                }
            });
        }

        System.out.println("shut down");
//      System.exit(-1);

    }

}
  • 實驗收貨: 
    1.spring mvc 預設controller是單例項(通過註解@Scope(“prototype”)變了多例項); 
    2.單例項時非執行緒安全,不要在controller中定義成員變數(例項變數); 
    3.單例項時,web容器啟動時便開始例項化controller,全域性唯此例項,每次訪問都使用此例項響應; 
    4.多例項時,每一次訪問,基本&多數(發現偶爾也會重複使用例項)會產出新例項對應響應; 
    5.單例項時,併發請求,訪問synchronized同步方法時,彼此阻塞影響(synchronized方法例項鎖); 
    6.多例項時,併發請求,訪問synchronized同步方法時,彼此不影響(synchronized方法例項鎖);

建議(此為一次給內部團隊分享):

  • 親自動手各種調整程式碼體驗
  • 通過此典型多執行緒環境多玩玩synchronized、ThreadLocal等