很多學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)換成@