JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用简介在多线程大师Doug Lea的贡献下,在JDK
JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用简介
在多线程大师Doug Lea的贡献下,在JDK1.5中加入了许多对并发特性的支持,例如:线程池。
一、简介线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:
ThreadPoolExecutor(int?corePoolSize,?int?maximumPoolSize,?
????????????????long?keepAliveTime,?TimeUnit?unit,?
????????????????BlockingQueue?workQueue,?
????????????????RejectedExecutionHandler?handler)
?? ?corePoolSize: ? 线程池维护线程的最少数量, ??
?? ?maximumPoolSize:线程池维护线程的最大数量
?? ?keepAliveTime: ?线程池维护线程所允许的空闲时间
?? ?unit: ? ? ? ? ? 线程池维护线程所允许的空闲时间的单位
?? ?workQueue: ? ? ?线程池所使用的缓冲队列
?? ?handler: ? ? ? ?线程池对拒绝任务的处理策略
?? 一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。
?? ?当一个任务通过execute(Runnable)方法欲添加到线程池时:?? ?如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。?? ?如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。?? ?如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。?? ?如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。??? ?也就是:处理任务的优先级为:?? ?核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。?? ?当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
?? ?unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:?? ? ? ? ? ? NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。
?? ?workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue?? ??? ?handler有四个选择:?? ?ThreadPoolExecutor.AbortPolicy() ? ? ? ??? ? ? 抛出java.util.concurrent.RejectedExecutionException异常?? ?ThreadPoolExecutor.CallerRunsPolicy() ???? ? ? 重试添加当前的任务,他会自动重复调用execute()方法?? ?ThreadPoolExecutor.DiscardOldestPolicy()?? ? ? 抛弃旧的任务?? ?ThreadPoolExecutor.DiscardPolicy() ? ? ??? ? ? 抛弃当前的任务
import?java.io.Serializable;
import?java.util.concurrent.ArrayBlockingQueue;
import?java.util.concurrent.ThreadPoolExecutor;
import?java.util.concurrent.TimeUnit;
public?class?TestThreadPool?{
????private?static?int?produceTaskSleepTime?=?2;
????private?static?int?consumeTaskSleepTime?=?2000;
????private?static?int?produceTaskMaxNumber?=?10;
????
?public?static?void?main(String[]?args)?{
????????//构造一个线程池????
????????ThreadPoolExecutor?threadPool?=?new?ThreadPoolExecutor(2,?4,?3,
????????????????TimeUnit.SECONDS,?new?ArrayBlockingQueue(3),
????????????????new?ThreadPoolExecutor.DiscardOldestPolicy());
????????for(int?i=1;i<=produceTaskMaxNumber;i++){
????????????try?{???????????????
????????????????//产生一个任务,并将其加入到线程池
????????????????String?task?=?"task@?"?+?i;
????System.out.println("put?"?+?task);
????threadPool.execute(new?ThreadPoolTask(task));
????
????//便于观察,等待一段时间
????????????????Thread.sleep(produceTaskSleepTime);
????????????}?catch?(Exception?e)?{
????????????????e.printStackTrace();
????????????}
????????}
?}
?/**
??*?线程池执行的任务
??*?@author?hdpan
??*/
????public?static?class?ThreadPoolTask?implements?Runnable,Serializable{
?????private?static?final?long?serialVersionUID?=?0;
?????//保存任务所需要的数据
????????private?Object?threadPoolTaskData;
????????
????????ThreadPoolTask(Object?tasks){
????????????this.threadPoolTaskData?=?tasks;
????????}
????????public?void?run(){
????????????//处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句
???System.out.println("start?.."+threadPoolTaskData);
?????????try?{
?????????????////便于观察,等待一段时间
????????????????Thread.sleep(consumeTaskSleepTime);
????????????}?catch?(Exception?e)?{
????????????????e.printStackTrace();
????????????}
????????????threadPoolTaskData?=?null;
????????}
????????public?Object?getTask(){
????????????return?this.threadPoolTaskData;
????????}
????}
}说明:1、在这段程序中,一个任务就是一个Runnable类型的对象,也就是一个ThreadPoolTask类型的对象。2、一般来说任务除了处理方式外,还需要处理的数据,处理的数据通过构造方法传给任务。3、在这段程序中,main()方法相当于一个残忍的领导,他派发出许多任务,丢给一个叫 threadPool的任劳任怨的小组来做。?? ?这个小组里面队员至少有两个,如果他们两个忙不过来, 任务就被放到任务列表里面。?? ?如果积压的任务过多,多到任务列表都装不下(超过3个)的时候,就雇佣新的队员来帮忙。但是基于成本的考虑,不能雇佣太多的队员, 至多只能雇佣 4个。?? ?如果四个队员都在忙时,再有新的任务, 这个小组就处理不了了,任务就会被通过一种策略来处理,我们的处理方式是不停的派发, 直到接受这个任务为止(更残忍!呵呵)。?? ?因为队员工作是需要成本的,如果工作很闲,闲到 3SECONDS都没有新的任务了,那么有的队员就会被解雇了,但是,为了小组的正常运转,即使工作再闲,小组的队员也不能少于两个。
4、通过调整 produceTaskSleepTime和 consumeTaskSleepTime的大小来实现对派发任务和处理任务的速度的控制, 改变这两个值就可以观察不同速率下程序的工作情况。5、通过调整4中所指的数据,再加上调整任务丢弃策略, 换上其他三种策略,就可以看出不同策略下的不同处理方式。6、对于其他的使用方法,参看jdk的帮助,很容易理解和使用。
发邮件示例:
在普通的web应用中,发送邮件应该只能算小任务,而使用jms来发送邮件有点杀鸡用牛刀的味道,那么如果能建立一个线程池来管理这些小线程并重复使用他们,应该来说是一个简单有效的方案,我们可以使用concurrent包中的Executors来建立线程池,Executors是一个工厂,也是一个工具类,我把它的api的介绍简单的翻译了一下(如果翻译有误请大家不要吝啬手中的砖头)
/**?
?*?由spring管理的线程池类,返回的ExecutorService就是给我们来执行线程的?
*如果不交给spring管理也是可以的,可以使用单例模式来实现同样功能,但是poolSize???*要hardcode了?
?*?@author?张荣华(ahuaxuan)?
*?@version?$Id$?
?*/??
public?class?EasyMailExecutorPool?implements?InitializingBean?{??
??
??//线程池大小,spring配置文件中配置??
??private?int?poolSize;??
??private?ExecutorService?service;??
??
??public?ExecutorService?getService()?{??
????return?service;??
??}??
??
??public?int?getPoolSize()?{??
????return?poolSize;??
??}??
??
??public?void?setPoolSize(int?poolSize)?{??
????this.poolSize?=?poolSize;??
??}??
??
??/**?
???*?在?bean?被初始化成功之后初始化线程池大小?
???*/??
??public?void?afterPropertiesSet()?throws?Exception?{??
????service?=?Executors.newFixedThreadPool(poolSize);??
??}??
} ?
这样我们就初始化了线程池的大小,接下来就是如何使用这个线程池中的线程了,我们看看MailService是如何来使用线程池中的线程的,这个类中的代码我已经作了详细的解释?代码
/**?
?*?用来发送?mail?的?service,?其中有一个内部类专门用来供线程使用?
?*?@author?张荣华(ahuaxuan)?
?*?@since?2007-7-11?
?*?@version?$Id$?
?*/??
public?class?EasyMailServieImpl?implements?EasyMailService{??
??private?static?transient?Log?logger?=?LogFactory.getLog(EasyMailServieImpl.class);???
????
??//注入MailSender??
??private?JavaMailSender?javaMailSender;??
????
??//注入线程池??
??private?EasyMailExecutorPool?easyMailExecutorPool;??
????
??//设置发件人??
??private?String?from;??
????
??public?void?setEasyMailExecutorPool(EasyMailExecutorPool?easyMailExecutorPool)?{??
????this.easyMailExecutorPool?=?easyMailExecutorPool;??
??}??
??
??public?void?setJavaMailSender(JavaMailSender?javaMailSender)?{??
????this.javaMailSender?=?javaMailSender;??
??}??
????
??public?void?setFrom(String?from)?{??
????this.from?=?from;??
??}??
??
??/**?
???*?简单的邮件发送接口,感兴趣的同学可以在这个基础上继续添加?
???*?@param?to?
???*?@param?subject?
???*?@param?text?
???*/??
??public?void?sendMessage(EmailEntity?email){??
????if?(null?==?email)?{??
??????if?(logger.isDebugEnabled())?{??
????????logger.debug("something?you?need?to?tell?here");??
??????}??
??????return;??
????}??
????SimpleMailMessage?simpleMailMessage?=?new?SimpleMailMessage();??
??????
????simpleMailMessage.setTo(email.getTo());??
????simpleMailMessage.setSubject(email.getSubject());??
????simpleMailMessage.setText(email.getText());??
????simpleMailMessage.setFrom(from);??
??????
????easyMailExecutorPool.getService().execute(new?MailRunner(simpleMailMessage));??
??}??
????
??/**?
???*?发送复杂格式邮件的接口,可以添加附件,图片,等等,但是需要修改这个方法,?
???*?如何做到添加附件和图片论坛上有例子了,需要的同学搜一下,?
???*?事实上这里的text参数最好是来自于模板,用模板生成html页面,然后交给javamail去发送,?
???*?如何使用模板来生成html见?{@link?http://www.iteye.com/topic/71430?}?
???*??
???*?@param?to?
???*?@param?subject?
???*?@param?text?
???*?@throws?MessagingException?
???*/??
??public?void?sendMimeMessage(EmailEntity?email)?throws?MessagingException?{??
????if?(null?==?email)?{??
??????if?(logger.isDebugEnabled())?{??
????????logger.debug("something?you?need?to?tell?here");??
??????}??
??????return;??
????}??
????MimeMessage?message?=?javaMailSender.createMimeMessage();??
????MimeMessageHelper?helper?=?new?MimeMessageHelper(message);??
??????
????helper.setTo(email.getTo());??
????helper.setFrom(from);??
????helper.setSubject(email.getSubject());??
??????
????this.addAttachmentOrImg(helper,?email.getAttachment(),?true);??
????this.addAttachmentOrImg(helper,?email.getImg(),?false);??
??????
????//这里的text是html格式的,?可以使用模板引擎来生成html模板,?velocity或者freemarker都可以做到??
????helper.setText(email.getText(),true);??
??????
????easyMailExecutorPool.getService().execute(new?MailRunner(message));??
??}??
????
??/**?
???*?添加附件或者是图片?
???*?@param?helper?
???*?@param?map?
???*?@param?isAttachment?
???*?@throws?MessagingException?
???*/??
??private?void?addAttachmentOrImg(MimeMessageHelper?helper,?Map?map,?boolean?isAttachment)?throws?MessagingException?{??
????for?(Iterator?it?=?map.entrySet().iterator();?it.hasNext();)?{??
??????Map.Entry?entry?=?(Map.Entry)?it.next();??
??????String?key?=?(String)?entry.getKey();??
??????String?value?=?(String)?entry.getValue();??
??????if?(StringUtils.isNotBlank(key)?&&?StringUtils.isNotBlank(value))?{??
????????FileSystemResource?file?=?new?FileSystemResource(new?File(value));??
????????if?(!file.exists())?continue;??
????????if?(isAttachment)?{??
??????????helper.addAttachment(key,?file);??
????????}?else?{??
??????????helper.addInline(key,?file);??
????????}??
??????}??
????}??
??}??
????
??/**?
???*?用来发送邮件的?Runnable,?该类是一个内部类,之所以使用内部类,而没有使用嵌套类(静态内部类),?
???*?是因为内部类可以之间得到?service?的?javaMailSender?
???*?每次发送邮件都会从线程池中取一个线程,?然后进行发邮件操作?
???*?@author?ahuaxuan?
???*/??
??private?class?MailRunner?implements?Runnable?{??
????SimpleMailMessage?simpleMailMessage;??
????MimeMessage?mimeMessage;???
??????
????/**?
?????*?构造简单文本邮件?
?????*?@param?simpleMailMessage?
?????*/??
????public?MailRunner(SimpleMailMessage?simpleMailMessage)?{??
??????if?(mimeMessage?==?null)?{??
????????this.simpleMailMessage?=?simpleMailMessage;??
??????}??
????}??
??????
????/**?
?????*?构造复杂邮件,可以添加附近,图片,等等?
?????*?@param?mimeMessage?
?????*/??
????public?MailRunner(MimeMessage?mimeMessage)?{??
??????if?(simpleMailMessage?==?null)?{??
????????this.mimeMessage?=?mimeMessage;??
??????}??
????}??
??????
????/**?
?????*?该方法将在线程池中的线程中执行?
?????*/??
????public?void?run()?{??
??????try?{??
????????if?(simpleMailMessage?!=?null)?{??
??????????javaMailSender.send(this.simpleMailMessage);??
????????}?else?if?(mimeMessage?!=?null)?{??
??????????javaMailSender.send(this.mimeMessage);??
????????}??
????????????????
????????????}?catch?(Exception?e)?{??
??????????????if?(logger.isDebugEnabled())?{??
????????????????logger.debug("logger?something?here",?e);??
??????????????}??
????????????}???????
????}??
??}??
} ?
MailService中的EmailEntity是对邮件的抽象(我只使用了失血模型,事实上我们也可以让这个EmailEntity来实现Runnable接口,这样Service中的内部类就可以去掉了,同时service中的大部分代码就要搬到EmailEntity及其父类里了,大家更倾向于怎么做呢?),代码如下:?代码
/**?
?*?该类是对邮件的抽象,邮件有哪些属性,这个类就有哪些属性?显然这个只是一个例子,?
?*?这个例子中附带mimemessage发送所需的附件或者图片(如果有的话)?
?*?需要使用的同学自己扩展?
?*??
?*?@author?张荣华(ahuaxuan)?
*?@version?$Id$?
?*/??
public?class?EmailEntity?{??
??
??String?to;??
??
??String?subject;??
??
??String?text;??
??
??//邮件附件??
??Map?