• 当你想在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;
        }
    
    }

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

    让我们继续前行

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

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

  • 相关阅读:
    Excel文件上传
    SAP 中如何寻找增强
    MySQL性能优化的最佳经验,随时补充
    PHP编程效率的20个要点
    php性能优化
    AngularJS API之$injector ---- 依赖注入
    AngularJS API之extend扩展对象
    AngularJS API之equal比较对象
    AngularJS API之isXXX()
    AngularJS API之toJson 对象转为JSON
  • 原文地址:https://www.cnblogs.com/killbug/p/2966889.html
Copyright © 2020-2023  润新知