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 

你可能感兴趣的:(JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用简介)