手记

Spring多线程批量发送邮件(ThreadPoolTaskExecutor)

1,需求:使用多线程批量发送邮件

需要批量发送邮件大概400封左右,但是因为发送邮件受网络限制,所以经常导致等待超时。所以就想到了使用多线程来发邮件,因为是异步的所以返回结果不受发邮件影响。

2,思路:使用spring的ThreadPoolTaskExecutor,用10个线程循环400个任务,完成任务后关闭

首先创建一个发邮件的Task(任务),只是负责发邮件。

然后创建一个执行任务的类,让ThreadPoolTaskExecutor循环执行

创建一个ThreadPoolTaskExecutor的bean配置交给spring管理

3,代码:SendEmailTask.java(任务类) SendEmailThread.java(执行任务类) email.xml(ThreadPoolTaskExecutor的配置)

SendEmailTask.java:

package com.XXX.core.base.utils.task;import com.XXX.core.base.utils.EmailUtil;/** 
* 发送邮件任务类
* @author chenminchang
* @date 2017年3月20日 下午4:40:42 
*/public class SendEmailTask implements Runnable {	
	private String toemail;	private String title;	private String content;	
	public SendEmailTask(String toemail, String title, String content){		this.toemail = toemail;		this.title = title;		this.content = content;
	}	
	@Override
	public void run() {
		EmailUtil.send(toemail, title, content);//send是自行封装的发邮件的方法
	}

}

SendEmailThread.java:

package com.XXX.core.base.utils.task;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;/** 
* 多线程批量发送邮件
* @author chenminchang
* @date 2017年3月20日 下午4:54:35 
*/public class SendEmailThread {	private static Logger log = LoggerFactory.getLogger(SendEmailThread.class);	private String[] toEmails;//需要发送的邮箱
	private String title;//需要发送的标题
	private String content;//需要发送的内容
	private ApplicationContext ctx =  new ClassPathXmlApplicationContext("classpath*:**/applicationContext**.xml");	private ThreadPoolTaskExecutor poolTaskExecutor = (ThreadPoolTaskExecutor)ctx.getBean("threadPool");	
	public SendEmailThread(String title, String content, String... toEmails) {		this.toEmails = toEmails;		this.title = title;		this.content = content;
	}	
	/**
	 * 使用多线程执行任务
	 * 
	 * @author chenminchang
	 * @create 2017年3月22日上午10:50:31
	 */
	public void runTask() {		if (poolTaskExecutor == null) {
			log.debug("the poolTaskExecutor is null");
		} else {			if (toEmails != null && toEmails.length > 0) {				if (toEmails.length < poolTaskExecutor.getCorePoolSize())//当任务数小于创建的线程
					poolTaskExecutor.setCorePoolSize(toEmails.length);				for (String email : toEmails) {					try {
						poolTaskExecutor.execute(getNextTask(email, this.title , this.content));
					} catch (Exception ex) {
						 ex.printStackTrace();  
					}
				}
			}
		}
	}	/**
	 * 获取新任务
	 * @param toemail
	 * @param title
	 * @param content
	 * @return
	 * @author chenminchang
	 * @create 2017年3月20日下午5:15:07
	 */
	private Runnable getNextTask(String toemail, String title, String content) {		return new SendEmailTask(toemail, title, content);
	}
	
}

 threadPool.xml: 将此文件添加到applicationContent.xml相同的文件夹下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="http://www.springframework.org/schema/beans  
                        http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="threadPool"
		class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
		<!-- 核心线程数,默认为1 -->
		<property name="corePoolSize" value="10" />

		<!-- 最大线程数,默认为Integer.MAX_VALUE -->
		<property name="maxPoolSize" value="100" />

		<!-- 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE -->
		<property name="queueCapacity" value="500" />

		<!-- 线程池维护线程所允许的空闲时间,默认为60s -->
		<property name="keepAliveSeconds" value="60" />

		<!-- 完成任务自动关闭 , 默认为false-->
		<property name="waitForTasksToCompleteOnShutdown" value="true" />

		<!-- 核心线程超时退出,默认为false -->
		<property name="allowCoreThreadTimeOut" value="true" />

		<!-- 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 -->
		<property name="rejectedExecutionHandler">
			<!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 -->
			<!-- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 -->
			<!-- DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
			<!-- DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
			<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
		</property>
	</bean>
</beans>


这里需要注意的是如果没有allowCoreThreadTimeOut这项配置,即使waitForTasksToCompleteOnShutdown设为true,线程完毕是不会关闭的,只是会转为驻留状态,还有一点是每次创建的线程都会产生新的线程,老线程不关闭,新线程重新生成,这样导致线程越来越多。大家可以根据Java VisualVM去测试,所以必须加上allowCoreThreadTimeOut属性
然后需要注意的是在 applicationContent.xml中引入threadPool.xml,在applicationContext.xml中加入 <import resource="threadPool.xml" />

4,测试SendEmailThreadTest.java

package com.XXX.core.base.utils.task;import java.util.ArrayList;import java.util.List;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import com.XXX.core.base.utils.EmailUtil;/** 
* 
* @author chenminchang
* @date 2017年3月20日 下午6:39:42 
*/@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = { "classpath*:**/applicationContext**.xml" })public class SendEmailThreadTest {	/**
	 * 测试线程批量发送邮件
	 * 
	 * @author chenminchang
	 * @create 2017年3月22日上午11:59:48
	 */
	@Test
	public void testSendEmailThread() {
		List<String> toEmailList = new ArrayList<>();		for (int i = 0; i < 50; i++) {
			toEmailList.add("你的@邮箱");
		}
		EmailUtil.sendBatchUseThread("注册用户", "您已成为注册用户", toEmailList.toArray(new String[toEmailList.size()]));
	}
}

5,结果


0人推荐
随时随地看视频
慕课网APP