new Thread().start内部类的使用场景?

下面的这个方法是发邮件的抽象出来的一个公用方法:

String[] to 表示收件人列表;
subject  邮件主题;
templateName 邮件末班,用velocity写的,
Map params 参数,用来填充velocity中的某些字段取值的
public void sendHtmlWithTemplate(String[] to, String subject, String templateName, Map<String, Object> params) {
    final MimeMessage mimeMessage = mailSender.createMimeMessage();
    try {
        final MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage);
        messageHelper.setFrom(simpleMailMessage.getFrom());
        if (ENV_ONLINE.equals(environment)) {
            messageHelper.setTo(to);
            messageHelper.setSubject(subject);
        } else {
            messageHelper.setTo(adminEmail);
            messageHelper.setSubject(subject + Arrays.asList(to));
        }
        messageHelper.setSentDate(new Date());
        final String content = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, templateName, "UTF-8",
                                                                           params);
        final String[] logTo = to;
        messageHelper.setText(content, true);
        new Thread() {

            @Override
            public void run() {
                mailSender.send(mimeMessage);
                logger.error("Mailsentto: " + Arrays.asList(logTo) + "\nContent: " + content);
            }
        }.start();
    } catch (Exception e) {
        logger.error("emailServiceError error:" + e.getMessage(), e);
    }
}

在上面的发邮件的代码中,使用了内部类如下:

new Thread() {
    @Override
    public void run() {
        mailSender.send(mimeMessage);
        logger.error("Mailsentto: " + Arrays.asList(logTo) + "\nContent: " + content);
    }
}.start();

我觉得在这个地方做这个控制是很恰当的吧,为什么team leader让我删掉这个new Thread()的部分。
原话:“把new Thread全部删掉,这些邮件发送不了的bug都没有暴露出来; 邮件发送模块的代码采用异步发送方式,失去异常事务回滚的能力,new Thread要全部删除;” 不懂是什么意思啊?

问题:
1.为什么要去掉new Thread()?
2.在什么样的并发业务场景下,需要使用new Thread()这样的方式?有没有更好的解决方法?
3.在并发场景下,使用new Thread().start()的方式有什么弊端?每次new Thread新建对象性能会很差么?邮件服务也是当触发某个业务规则的时候,可能需要大量发送一下邮件,用线程池好不好呢?

阅读 20.3k
4 个回答

@有明 已经说了下匿名内部类比较蛋疼的地方。我再结合你的业务来说一下:

  1. 根据现有的代码逻辑,我们邮件服务对外暴露的应该是sendHtmlWithTemplate这个方法,而不是 mailSender.send,所以在我们对外暴露的sendHtmlWithTemplate这个方法里面就不需要再重新启一个线程异步去发送邮件,因为对于调用方,其实你需要告诉它这次发送邮件是否成功,如果失败了/异常了,失败的原因是什么。如果按照你现有的方案的话,可能给外面感觉就是,不管邮件发送成功或者失败,外面都是没有感知的,意味着不能将这封邮件持久化,并且过一段时间再重试。所以异步发送邮件应该交给你的调用方,而不是在你的方法里面

  2. 假设我们的发送邮件可能是一个很耗时的动作,那么按照每来一封邮件就启一个线程去做的话,意味这到时候系统里面会产生很多线程。那么就意味会有很多线程切换,线程的切换,线程的切换就会有阻塞和唤醒,这些都涉及到用户态和内核态的转换或者是线程的上下文的切换,都是很耗CPU的,极端情况如果系统线程不段堆积的话,会导致整个服务DOWN机,服务处于不可用的状态。new Thread()确实是一个很昂贵的操作

  3. 如果正确的做异步发送邮件,确实需要放到线程池里面做

  4. 更加的好的方法,一种就是通过MQ来发送邮件消息(考虑消息常见蛋疼的问题1.消息是否会丢失2.消息重发了怎么办),单独交给一台发送邮件的服务器去做。还有一种就是先存入数据库表,如果时效性要求不是很高的话,到时候启一个定时器慢慢发。

匿名对象很方便,但匿名对象也是魔鬼,使用不好就能让你的应用崩塌。就拿你这里做例子,如果你没有控制好发送邮件的参数,比如发送时超时了就继续等待,那这个线程可就变成了孤魂野鬼了,找也找不到,关也关不掉,这是一个程序员该做的事情吗?
你问为何你的头让你把这里去掉,我只能说是他还不够相信的你能力,所以不敢让你操纵这个魔鬼。
你问什么场景下适合,我只能说没有适合的场景,只有有能力的人。

如果要发大量的邮件,可以做个队列,线程太多的话,恐怕不太好。

不好意思,现在才看到问题。

具体参考@iMouseWu@有明 的答案。

如果你有写过js,你可以理解这里的new Thread()相当于ajax请求,是属于异步行为,在这个线程任务还没执行完之前,方法已经返回给调用方了(而且不会抛出发送邮件产生的异常),这也就是你leader说的“这些邮件发送不了的bug都没有暴露出来”

考虑并发量大的话可以采用redis+kafka(或其他消息组件),用消息的形式去通知邮件服务发送邮件。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏