很多学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)换成@