当你想在web应用中使用线程的时候-我们到底能走多远系列(24)

我们到底能走多远系列(24)

先不扯淡,先推荐:

  如果你热爱英文技术原文的话,这个推荐的网站绝对让你会想抱一抱他:http://www.salttiger.com/ (也许你早就知道啦) 再一次感谢那些乐于分享和贡献的勇士们,虽然互联网上我们互不相识,却通过知识,我们建立了某种超越空间时光的特殊关系,想想,这真的很有趣。

扯淡:

  最近朋友在老家工作量一年,又跑来城市奋斗。可是纳闷的是我数来数去,当时留在城市的人数正在逐年的下降,可这货怎么还会来呢?

最近,想比较深入的学习事务,可是看了好多文章,却越看越糊涂,有想起去看别的东西,有点三心两意的感觉了。真心希望有人带一下,轻松一点,唉。神,赐我一个大牛吧!

现在的公司,虽然是国内的,工作管理上较为开放,很多事可以自己决定,有时候自己会准备好几个方案,和同事讨论一下,选个比较优的,再去写代码,到也不错。

 

主题:

  初入web的时候,我们总是会被教育,web应用无需关心线程的问题,学好基本的框架,就可以上手啦。

  其实实际项目中使用多线程的情况是很正常的,在业务复杂的应用程序中,比如如果一个业务非常耗时,我们只好采用异步的方式,避免影响web端的展示,再有定时监控数据库字段的变化的业务,或者是batch处理(半夜处理数据库.....)也就是定时任务啦等等。

我就把最近遇到的问题展现给各位,希望大家能给点好的意见,我都会尝试使用,并应用到项目中去。

1,发邮件问题

  项目中,注册完毕后,需要向用户的邮箱发送邮件,开始的代码就是把发邮件的逻辑封装在service层,然后action层调用完毕后,返回页面,展现页面。实际测试还没有发现页面跳转太慢的情况,但是为了安全起见,还有一个原则就是我们不要把一些会抛诸runtimeException的逻辑放在和展现页面的逻辑一起,异步是唯一的选择。

多线程的实现是利用spring的框架:

线程池bean配置:

      <bean id="mailTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="3"/>
        <property name="maxPoolSize" value="10"/>
    </bean>

直接把mailTaskExecutor注入进service层,就可以使用啦!

一下是发送邮件的简单代码:

    public boolean sendMail4D(String email,String basePath, String cstmName,String userName,String passWord) {
        final MailSenderInfo mailInfo = getMailInfo(email);
        final Map model = new HashMap();
        model.put("basePath", basePath);
        model.put("cstmName", cstmName);
        model.put("username", userName);
        model.put("password", passWord);
        model.put("dealerLogin", dealerLogin);
        model.put("customerTel", customerTel);
        model.put("cusEmailAddress", cusEmailAddress);
        mailTaskExecutor.execute(new Runnable(){
               public void run(){
                   String result;
                try {
                    result = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine,
                                                                "dealer-reg-send-mail.vm", model);
                    mailInfo.setContent(result);
                    SimpleMailSender sms = new SimpleMailSender();
                    sms.sendHtmlMail(mailInfo);// 发送文体格式
                } catch (Exception e) {
                    log.error("send mail failed,there has a Exception");
                    log.error(e.toString());
                    e.printStackTrace();
                }

               }
        });
        return true;
    }

  这样以来,在action层调用这个方法后就不用等待,邮件发送完毕后的返回了,唯一的坏处是,我们不知道邮件是否发送成功,这可能需要更多其他的代码来弥补了。

 

2,数据库字段监控问题

问题描述:

比如我们进行一个发布评论的操作,发布这个过程是通过webservice来调用另一个系统来实现的,在调用前我们把这个数据的字段置为发布中,对方回调成功后就置为发布成功,失败就是发布失败,但是有一种情况那就是对方出现异常,回调没来调,这样会导致这条数据一直为发布中... 这样的数据就需要我这边来判断,然后把它置为发布失败!

开始的想法:

每次调用对方的发布接口时,都启用一个线程,这个线程每隔一分钟检查数据库中这条数据的标志位,检查三次,如果始终是发布中,就把它置为发布失败

ok,开始去实现了,想到前面提到的spring框架提供的线程池,也不是很难了吧。

一下是线程的代码示例:(spring的线程配置与前面差不多)

import org.apache.log4j.Logger;

import com.syezon.webapp.constant.BusinessConstants;
import com.syezon.webapp.dao.ReleaseDao;
import com.syezon.webapp.model.Release;

public class CheckAdvStatusThread extends Thread{
    
    public static Logger log = Logger.getLogger(CheckAdvStatusThread.class);
    private ReleaseDao releaseDao;
    private Long releaseId;
    
    public CheckAdvStatusThread(Long releaseId, ReleaseDao releaseDao){
        this.releaseId = releaseId;
        this.releaseDao = releaseDao;
    }

    public void setReleaseDao(ReleaseDao releaseDao) {
        this.releaseDao = releaseDao;
    }

    public void run() {
        
        int i = 0;
        for ( ; i < 3; i++) {
            try {
                // 半分钟
                Thread.sleep(30000);
                
                Release release = releaseDao.getById(releaseId);
                if(release.getStatus() == BusinessConstants.RELEASE_STATUS_PUBING){
                    continue;
                }else{
                    break;
                }
            } catch (InterruptedException e) {
                log.error("there has a InterruptedException");
                e.printStackTrace();
            }
        }
        // 一分半钟
        if(i == 3){
            releaseDao.setStatus(releaseId, BusinessConstants.RELEASE_STATUS_PUB_FAIL);
        }
    }

}

代码也没什么好解释了,特别要注意的是:构造方法,dao层的bean是通过构造时进来的,为什么不利用spring注入呢?事实上,试过的朋友应该多知道,在线程类中是无法注入的,可能线程启动的方式绕过了一个正常实例产生时需要的流程吧,解决方法有:

在用多线程的时候,里面要用到Spring注入服务层,或者是逻辑层的时候,一般是注入不进去的。具体原因应该是线程启动时没有用到Spring实例不池。所以注入的变量值都为null。

如果在run方法里面加载application.xml,来取得bean时,由于重复加载,一般情况下会耗尽内存,抛出内存溢出错误。所以这的确是一个很头痛的问题。

有一个方法可以解决注不进去的问题。就是在声明变量的时候,前面加static。这样在初始化的时候它就会加载application.xml,得到bean。

关于这个问题的根本机制没有作深入的研究,好在问题解决了。

从这个例子体会到林信良说过的,没有一个技术是完美的,不要为了Spring而Spring。不要为了注入而注入。

我没有使用以上方式是因为,我尝试了一下,不可行啊,但是我网上寻找的答案太过一直,所以我认为我是个特例,如果你也遇到类似的问题,大可以先尝试一下上面的方法。

 

以上方法的问题:并发量大的时候会导致大量线程的启用和销毁,在3分钟的时间里,真的难以想象不断创建线程会发生什么,想想也有点怕怕啊!

亲,如果是你,你怕吗?

 

下班前的指导:

上级给出的意见是这样,采用一个队列(说白了,不就是LinkList嘛),然后启动一个线程,这个线程对着个队列进行检测。每次发布的时候向这个队列里塞信息,线程根据队列中的信息,判断发布中的状态是否维持到了超时的范围,就把它置为发布失败,队列删除这信息

上面的想法,其实很不错,如此解决了第一种方式带来的大部分问题。

回去想了好久,我想问一下你们,你们有类似的经验吗,给点提示,例子什么的啊~~

 

经过和同事的沟通,对方建议采用定时器更加靠谱!

目前,使用的是定时器方式解决的:

spring也支持定时器嘛,配置如下:

      <!-- 需要执行的任务 -->
    <bean id="checkAdvStatusJob" class="org.springframework.scheduling.quartz.JobDetailBean">
        <property name="jobClass" value="com.syezon.webapp.util.CheckAdvStatusMonitor"/>
        <property name="jobDataAsMap">   
        <map>
            <entry key="releaseDao">
                <ref bean="releaseDao"/>
            </entry>
        </map>  
    </property>
    </bean>
    <!-- 对任务的参数的设置 -->
    <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
        <property name="jobDetail" ref="checkAdvStatusJob" />
        <property name="startDelay" value="180000" /><!--启动3分钟后再开始-->
        <property name="repeatInterval" value="180000" /><!--每3分钟跑一次-->
    </bean>
    <!-- 启动任务 -->
    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <!--<property name="triggers" ref="cronTrigger" />-->
        <property name="triggers" ref="simpleTrigger" />
    </bean>
    

  注意checkAdvStatusJob的配置,正真的逻辑我们是写在jobClass里的。注入到jobClass的dao层bean只能通过上面的方式实现,不能用普通spring的property 去实现哦!releaseDao是注入到jobClass里的!下面的配置就不解释啦。

 

CheckAdvStatusMonitor类,简单的示例:
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import com.syezon.webapp.constant.BusinessConstants;
import com.syezon.webapp.dao.ReleaseDao;
import com.syezon.webapp.model.Release;

public class CheckAdvStatusMonitor extends QuartzJobBean{
    public static Logger log = Logger.getLogger(CheckAdvStatusMonitor.class);
    private ReleaseDao releaseDao;
    
    @Override
    protected void executeInternal(JobExecutionContext arg0)
            throws JobExecutionException {
        
        log.info("CheckAdvStatusMonitor begin");
        
        // 查询全部发布中状态的发布信息
        List<Release> releases = releaseDao.getByStatus(BusinessConstants.RELEASE_STATUS_PUBING);
        if(releases == null || releases.size() <= 0){
            log.info("CheckAdvStatusMonitor end - there has no publishing release");
            return;
        }
        for (Release release : releases) {
            Date createDate = release.getReleaseTime();
            Date currentDate = new Date();
            long l = currentDate.getTime() - createDate.getTime();
            // 超过三分钟
            if(l > 180000){
                log.info("CheckAdvStatusMonitor change a status");
                releaseDao.setStatus(release.getId(), BusinessConstants.RELEASE_STATUS_PUB_FAIL);
                log.info("CheckAdvStatusMonitor change a status, releaseId = release.getId()");
            }
        }
        log.info("CheckAdvStatusMonitor end");
    }

    public void setReleaseDao(ReleaseDao releaseDao) {
        this.releaseDao = releaseDao;
    }

}

 

好了,事情描述完啦,亲,你有没有好的建议? 

 

 

 

让我们继续前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不会成功。
共勉。

 

 

posted on 2013-03-18 22:03  每当变幻时  阅读(5794)  评论(9编辑  收藏  举报

导航