Service層的性能優化

很多學J2EE方向的同學都接觸過S2SH,即傳統的三大框架,學習這三個經典技術的重點就是挖原理和細節,慢慢地我們就能形成一套思想,以幫助理解其他新框架和新技術。學習技術本身並不難,設計技術方案纔是難點,爲什麼要這麼設計,這樣設計的哲學依據又在哪?


不難發現:Struts2中控制層的action是多例的,在action層一般引用了邏輯層的單例service,而在邏輯層中又引用了單例的dao。因爲作爲控制層,action必須接受前端傳遞的參數,而Struts2又基於攔截器思想,建立HTTP請求後要經由複雜的攔截器才能到達控制層進行處理。這些參數就是action中的成員變量,如果併發請求action而action又是單例的,這不是會發生非線程安全麼?而service頂多就只有dao的引用作爲成員變量,dao本身也只有對操作數據庫的包裝類的引用充當成員變量,所以沒有涉及到非線程安全,即本身就是線程安全。所以我們spring框架在默認情況下的bean都是單例模式。每一次訪問方法進行處理都要new對象和回收對象,浪費系統資源,而單例模式就是可以解決這個問題。

以下的實驗是在網絡環境不怎麼好的情況下進行的,把時長拉得更加明顯。
非單例模式的情況:
controller層(Jersey框架,基於REST風格的Web Service)

@GET
@Path("history_station_user")
@Produces(MediaType.APPLICATION_JSON)
public String getHistoryStationUserInfo(
        @QueryParam("area_list") @DefaultValue("null") String areaList,
        @QueryParam("year_month") @DefaultValue("null") String yearMonth) {
    if (areaList.equals("null") || areaList.length() == 0) {
        return JsonUtil.getResponse(Status.PARA_ERROR).toString();
    }
    String[] area = areaList.split(",");
    UserService userService = new UserServiceImpl();//非單例
    List list = userService.getHistoryStationUserList(yearMonth, area);
    return JsonUtil.getDataResponse(Status.OK, list).toString();
}

service層(每次都new一個對象):

public class UserServiceImpl implements UserService {
    static HibernateDao dao = HibernateDaoImpl.getInstance();//空間換時間
    ...
}

該項目放在某外網中的局域網,通過端口映射到外網纔可訪問,所以接受速度有點慢。
(可右鍵“在新標籤頁中打開圖片”)
這裏寫圖片描述
我們可以看到,返回400KB左右的JSON數據。

第一次訪問月份爲2時,用時12.27s;
第一次訪問月份爲3時,用時6.82s;
第一次訪問月份爲4時,用時4.86s;
第二次訪問月份爲2時,用時1.31s;(第二次從二級緩存中讀取)
第二次訪問月份爲3時,用時868ms;
第二次訪問月份爲4時,用時775ms。

controller層(Jersey框架,基於REST風格的Web Service)

@GET
@Path("history_station_user")
@Produces(MediaType.APPLICATION_JSON)
public String getHistoryStationUserInfo(
        @QueryParam("area_list") @DefaultValue("null") String areaList,
        @QueryParam("year_month") @DefaultValue("null") String yearMonth) {
    if (areaList.equals("null") || areaList.length() == 0) {
        return JsonUtil.getResponse(Status.PARA_ERROR).toString();
    }
    String[] area = areaList.split(",");
    UserService userService = UserServiceImpl.getInstance();//單例
    List list = userService.getHistoryStationUserList(yearMonth, area);
    return JsonUtil.getDataResponse(Status.OK, list).toString();
}

service層(懶漢雙重鎖定單例模式):

public class UserServiceImpl implements UserService {
    private static HibernateDao dao = null;
    private voliate static UserServiceImpl instance = null;
    //懶漢式需要雙重鎖定解決可能的線程安全問題。
    public static UserServiceImpl getInstance() {//時間換空間
        if (instance == null) {
            synchronized (UserServiceImpl.class) {
                if (instance == null) {
                    instance = new UserServiceImpl();
                }
            }
        }
        return instance;
    }

    private UserServiceImpl() {
        dao = HibernateDaoImpl.getInstance();
    }
    ...
}

(可右鍵“在新標籤頁中打開圖片”)
這裏寫圖片描述
我們可以看到,返回400KB左右的JSON數據。

第一次訪問月份爲2時,用時9.32s;
第一次訪問月份爲3時,用時5.89s;
第一次訪問月份爲4時,用時4.78s;
第二次訪問月份爲2時,用時1.03s;(第二次從二級緩存中讀取)
第二次訪問月份爲3時,用時1.12s;
第二次訪問月份爲4時,用時748ms。

service層(餓漢單例模式):

public class UserServiceImpl implements UserService {
    static HibernateDao dao = null;
    //餓漢式沒有線程安全問題,缺點是類一加載就實例化,提前佔用系統資源
    private static UserServiceImpl instance = new UserServiceImpl();

    /**
     * 私有默認構造方法
     */
    private UserServiceImpl() {
        dao = HibernateDaoImpl.getInstance();
    }

    /**
     * 靜態工廠方法
     */
    public static UserServiceImpl getInstance() {
        return instance;
    }
    ...
}

(可右鍵“在新標籤頁中打開圖片”)
這裏寫圖片描述
我們可以看到,返回400KB左右的JSON數據。

第一次訪問月份爲2時,用時5.46s;
第一次訪問月份爲3時,用時4.19s;
第一次訪問月份爲4時,用時3.14s;
第二次訪問月份爲2時,用時329ms;(第二次從二級緩存中讀取)
第二次訪問月份爲3時,用時291ms;
第二次訪問月份爲4時,用時298ms。

經過三種方式的對比,我們發現經過二級緩存的性能優化後,採用的這三種策略也對性能的響應有影響,採用餓漢單例模式可以良好地提高響應速度。


作者: @nanphonfy
Email: nanphonfy (Nfzone) gmail.com 請將(Nfzone)換成@


發佈了90 篇原創文章 · 獲贊 26 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章