Java 创建用于发送电子邮件的后台线程

Java 创建用于发送电子邮件的后台线程,java,multithreading,Java,Multithreading,我需要在注册过程中发送一封电子邮件,因此我正在使用Java Mail API,这工作正常,但观察到 电子邮件处理过程花费了将近6秒(太长了),因此Ajax调用使用户等待响应的时间太长 因此,我决定使用后台线程发送电子邮件,这样用户就不必等待Ajax调用响应(Jersey REST Web服务调用) 我的问题是,在Web应用程序中为每个请求创建线程是一种好的做法吗 @Path("/insertOrUpdateUser") public class InsertOrUpdateUser {

我需要在注册过程中发送一封电子邮件,因此我正在使用Java Mail API,这工作正常,但观察到 电子邮件处理过程花费了将近6秒(太长了),因此Ajax调用使用户等待响应的时间太长

因此,我决定使用后台线程发送电子邮件,这样用户就不必等待Ajax调用响应(Jersey REST Web服务调用)

我的问题是,在Web应用程序中为每个请求创建线程是一种好的做法吗

@Path("/insertOrUpdateUser")
public class InsertOrUpdateUser {
        final static Logger logger = Logger.getLogger(InsertOrUpdateUser.class);
        @GET
        @Consumes("application/text")
        @Produces("application/json")
        public String getSalesUserData(@QueryParam(value = "empId") String empId
                        )
                        throws JSONException, SQLException {
                JSONObject final_jsonobject = new JSONObject();
            ExecutorService executorService = Executors.newFixedThreadPool(10);
                                executorService.execute(new Runnable() {
                                 public void run() {
                                         try {
                                                        SendEmailUtility.sendmail(emaildummy);
                                                } catch (IOException e) {
                                                        logger.error("failed",e);

                                                }
                                 }
                              });

                        }


                } catch (SQLException e) {

                } catch (Exception e) {


                }


                   finally {

                        }


                return response;
        }




}
这是我发送电子邮件的实用程序类

public class SendEmailUtility
{
    public static String sendmail(String sendto)
        throws IOException
    {
        String result = "fail";
        Properties props_load = getProperties();
        final String username = props_load.getProperty("username");
        final String password = props_load.getProperty("password");
        Properties props_send = new Properties();
        props_send.put("mail.smtp.auth", "true");
        props_send.put("mail.smtp.starttls.enable", "true");
        props_send.put("mail.smtp.host", props_load.getProperty("mail.smtp.host"));
        props_send.put("mail.smtp.port", props_load.getProperty("mail.smtp.port"));

        Session session = Session.getInstance(props_send,
            new javax.mail.Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication()
                {
                    return new PasswordAuthentication(username, password);
                }
            });

        try {
            Message message = new MimeMessage(session);
            message.setFrom(new InternetAddress(props_load.getProperty("setFrom")));
            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(sendto));
            message.setText("Some Text to be send in mail");
            Transport.send(message);
            result = "success";
        } catch (MessagingException e) {
            result = "fail";
            logger.error("Exception Occured - sendto: " + sendto, e);
        }
        return result;
    }
}

请告诉我这是否是在web应用程序中执行的最佳做法???

最佳做法是使用单个
ExecutorService
为所有请求提供线程池。您可能希望为ExecutorService配置非零但数量有限的线程


这里的想法是,在应用程序的整个生命周期中,您将拥有一些可重用的线程。您还可以获得额外的好处,即如果发送电子邮件时出现暂时性的减速(或暂停),那么最终不会有越来越多的线程,而是会有越来越多的工作(要发送的电子邮件)需要执行,这比额外线程占用的资源少很多。

在JavaWebSerer上手动创建
ExecutorService
是个坏主意。在每个请求的实现中创建10个线程

更好的解决方案是使用JEE7时使用
ManagedExecutorService
(),使用Spring()时使用
ThreadPoolTaskExecutor


如果您使用Tomcat,您应该阅读。

有很多方法可以处理它,因此这完全取决于您的应用程序服务器是否有足够的资源(内存、线程等)来处理您的实现,因此它使您成为决定采用哪种方法的最佳人选

因此,如果设计证明是合理的,那么产生并行线程来执行某件事情并不是一种坏做法,但通常应该使用受控线程

请注意,无论您使用
newSingleThreadExecutor()
还是
newFixedThreadPool(nThreads)
,在引擎盖下始终会创建一个
ThreadPoolExecutor
对象

我的建议是使用下面列表中的秒选项,即“受控线程数”,并在其中指定最大线程数,如您所见

每个请求一个线程 在这种方法中,将为来自GUI的每个传入请求创建一个线程,因此,如果您收到10个插入/更新用户的请求,那么将生成10个线程,这些线程将发送电子邮件

这种方法的缺点是无法控制线程的数量,所以可以以StackOverflowException结束,或者可能是内存问题

请确保关闭executor服务,否则将浪费JVM资源

// inside your getSalesUserData() method
ExecutorService emailExecutor = Executors.newSingleThreadExecutor();
        emailExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    SendEmailUtility.sendmail(emaildummy);
                } catch (IOException e) {
                    logger.error("failed", e);
                }
            }
        });
        emailExecutor.shutdown(); // it is very important to shutdown your non-singleton ExecutorService.
受控线程数 在这种方法中,会出现一些预定义数量的线程,这些线程将处理您的电子邮件发送要求。在下面的示例中,我启动了一个最多10个线程的线程池,然后我使用了
LinkedBlockingQueue
实现,这样可以确保如果有10个以上的请求,并且当前我所有的10个线程都很忙,那么多余的请求将排队,而不会丢失,这就是
LinkedBlockingQueue
实现
Queue
的优势

您可以在应用程序服务器启动时初始化singleton
ThreadPoolExecutor
,如果没有请求,则不会出现线程,这样做是安全的。事实上,我对prod应用程序使用了类似的配置

我使用的生存时间是1秒,所以如果一个线程在JVM中的理想时间超过1秒,那么它就会死亡

请注意,由于相同的线程池用于处理所有请求,所以它应该是单线程池,不要关闭此线程池,否则您的任务将永远不会执行

// creating a thread pool with 10 threads, max alive time is 1 seconds, and linked blocking queue for unlimited queuing of requests.
        // if you want to process with 100 threads then replace both instances of 10 with 100, rest can remain same...
        // this should be a singleton
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

        // inside your getSalesUserData() method
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    SendEmailUtility.sendmail(emaildummy);
                } catch (IOException e) {
                    logger.error("failed", e);
                }
            }
        });

电子邮件是从服务器端发送的,不是吗?因此,在哪个线程中运行它并不重要,因为它总是需要相同的时间。也许您想向用户显示一个循环的中间进度条,表明他的请求可能需要几秒钟才能完成。您是否也考虑了您的互联网速度?对于造成的混乱,很抱歉,在后台线程的帮助下,Ajax调用没有等待电子邮件进程,因此Ajax响应没有问题,但我关心的是正在创建的线程的数量。您的示例并没有显示为每个请求创建一个新线程。它显示了向线程池提交任务,这是一个更好的计划。@NathanHughes,所以下面这一行不是问题??ExecutorService ExecutorService=Executors.newFixedThreadPool(10);与我的问题相关,这似乎很有用,是否可以为我的案例配置ThreadPool?是的,只需在类级别将ExecutorService设置为静态(如记录器),并且在类加载并激活时将创建一次,直到应用程序停止。您可以实现一个
ServletContextListener
来明确管理生命周期,并确保正确关闭和清理(例如,您的应用程序将等待所有消息发送后再关闭)。我正在使用tomcat,非常感谢,我可以使用它作为完成此任务的起点。在最后一个选项中,ExecutorService是否也应该使用Singleton概念创建,还是可以为每个传入请求创建emailExecutor?@zee我在代码中提到-
//这应该是一个Singleton
。如果您执行的是
ExecutorService emailExecutor=Executors.newCachedThreadPool()
对于每个请求,基本上就是为eac创建一个新的线程池
// this should be a singleton
        ExecutorService emailExecutor = Executors.newCachedThreadPool();

        // from you getSalesUserData() method
        emailExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    SendEmailUtility.sendmail(emaildummy);
                } catch (IOException e) {
                    logger.error("failed", e);
                }
            }
        });