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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值