我们到底能走多远系列
当你想在web应用中使用线程的时候-我们到底能走多远系列(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; } }
好了,事情描述完啦,亲,你有没有好的建议?
让我们继续前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不会成功。
共勉。
Java
当你想在web应用中使用线程的时候-我们到底能走多远系列(24)
posted @ 2013-03-18 22:03 每当变幻时 阅读(63) | 评论 (0) 编辑
文件上传+截图+预览升级版-我们到底能走多远系列(23)
posted @ 2013-03-04 17:08 每当变幻时 阅读(1742) | 评论 (4) 编辑
文件下载-excel导出-我们到底能走多远系列(22)
posted @ 2013-02-04 19:37 每当变幻时 阅读(1820) | 评论 (7) 编辑
图片尺寸判断等-我们到底能走多远系列(21)
posted @ 2013-01-29 13:09 每当变幻时 阅读(1021) | 评论 (2) 编辑
图片上传+预览+剪切解决方案-我们到底能走多远系列(20)
posted @ 2013-01-16 12:47 每当变幻时 阅读(4292) | 评论 (28) 编辑
web应用图片存取体验-我们到底能走多远系列(19)
posted @ 2013-01-11 09:14 每当变幻时 阅读(1671) | 评论 (6) 编辑
ibatis 一对多 解决方案
posted @ 2013-01-04 21:54 每当变幻时 阅读(198) | 评论 (0) 编辑
关于spring和ibatis的整合
posted @ 2012-12-25 00:12 每当变幻时 阅读(278) | 评论 (0) 编辑
Java NIO深入(Buffer)-我们到底能走多远系列(18)
posted @ 2012-12-03 22:07 每当变幻时 阅读(1117) | 评论 (4) 编辑
Java NIO基础 -我们到底能走多远系列(17)
posted @ 2012-11-25 17:17 每当变幻时 阅读(1305) | 评论 (5) 编辑
理解IO_InputStream-我们到底能走多远系列(16)
posted @ 2012-11-18 21:50 每当变幻时 阅读(1148) | 评论 (3) 编辑
Java 阶段面试 知识点合集 - 我们到底能走多远系列(15)
posted @ 2012-11-13 23:37 每当变幻时 阅读(1628) | 评论 (2) 编辑
Tomcat加载类机制 - 我们到底能走多远系列(14)
posted @ 2012-11-11 01:05 每当变幻时 阅读(1416) | 评论 (6) 编辑
Tomcat容器结构及Pipeline机制 -我们到底能走多远系列(13)
posted @ 2012-11-03 20:11 每当变幻时 阅读(892) | 评论 (1) 编辑
java XML解析-我们到底能走多远系列(12)
posted @ 2012-10-29 23:30 每当变幻时 阅读(502) | 评论 (2) 编辑
InternalInputBuffer处理HTTP请求行-Tomcat源码-我们到底能走多远系列(11)
posted @ 2012-10-21 22:12 每当变幻时 阅读(963) | 评论 (2) 编辑
Tomcat StringManager阅读学习 -我们到底能走多远系列(10)
posted @ 2012-10-17 21:33 每当变幻时 阅读(1296) | 评论 (2) 编辑
Tomcat加载servlet类文件 -我们到底能走多远系列(9)
posted @ 2012-10-13 00:41 每当变幻时 阅读(976) | 评论 (0) 编辑
How Tomcat Works 学习-我们到底能走多远系列(8)
posted @ 2012-10-10 22:51 每当变幻时 阅读(1254) | 评论 (2) 编辑
Servlet-我们到底能走多远系列(7)
posted @ 2012-09-29 20:57 每当变幻时 阅读(1068) | 评论 (2) 编辑
Servlet的Context与Config
posted @ 2012-09-29 00:26 每当变幻时| 编辑
servlet原理2
posted @ 2012-09-28 00:21 每当变幻时| 编辑
多线程_传送带-我们到底能走多远系列(6)
posted @ 2012-09-09 15:24 每当变幻时 阅读(1073) | 评论 (3) 编辑
多线程_分析词频-我们到底能走多远系列(5)
posted @ 2012-09-02 22:27 每当变幻时 阅读(1237) | 评论 (5) 编辑
剪切文件_配置文件修改-我们到底能走多远系列(4)
posted @ 2012-08-26 19:27 每当变幻时 阅读(215) | 评论 (0) 编辑
BufferedInputStream-我们到底能走多远系列(3)
posted @ 2012-08-22 21:51 每当变幻时 阅读(1263) | 评论 (0) 编辑
遍历文件夹内的文件(我们到底能走多远系列2)
posted @ 2012-08-18 23:45 每当变幻时 阅读(233) | 评论 (0) 编辑
java IO 解析
posted @ 2012-08-16 21:55 每当变幻时 阅读(204) | 评论 (0) 编辑
java IO 流的学习(我们到底能走多远系列1)
posted @ 2012-08-15 21:47 每当变幻时 阅读(232) | 评论 (0) 编辑
java File 基础学习
posted @ 2012-08-09 23:20 每当变幻时 阅读(79) | 评论 (0) 编辑
servlet阅读
posted @ 2012-08-07 23:32 每当变幻时 阅读(129) | 评论 (0) 编辑
post and get
posted @ 2012-08-05 22:12 每当变幻时 阅读(97) | 评论 (0) 编辑
插入排序 来自《算法导论》
posted @ 2012-08-04 22:36 每当变幻时 阅读(27) | 评论 (0) 编辑
合并两个有序数组(重新开始)
posted @ 2012-08-03 23:07 每当变幻时 阅读(57) | 评论 (0) 编辑
Java参数传递问题
posted @ 2012-08-02 00:23 每当变幻时 阅读(928) | 评论 (0) 编辑
分治算法
posted @ 2012-07-31 23:15 每当变幻时 阅读(101) | 评论 (0) 编辑
选择排序(selection sort)
posted @ 2012-07-29 22:53 每当变幻时 阅读(29) | 评论 (0) 编辑
插入排序
posted @ 2012-07-29 19:58 每当变幻时 阅读(39) | 评论 (0) 编辑
数组排序(冒泡和快速)
posted @ 2012-02-13 21:17 每当变幻时 阅读(59) | 评论 (0) 编辑
final修饰符
posted @ 2012-02-08 21:11 每当变幻时 阅读(123) | 评论 (0) 编辑
hashmap可以用null为键值
posted @ 2012-01-12 22:19 每当变幻时 阅读(316) | 评论 (0) 编辑