@import url(http://m.tkk7.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
在多線程大師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