下面的这个方法是发邮件的抽象出来的一个公用方法:
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新建对象性能会很差么?邮件服务也是当触发某个业务规则的时候,可能需要大量发送一下邮件,用线程池好不好呢?
@有明 已經說了下匿名內部類比較蛋疼的地方。我再結合你的業務來說一下:
根據現有的程式碼邏輯,我們郵件服務對外暴露的應該是
sendHtmlWithTemplate
这个方法,而不是mailSender.send
,所以在我们对外暴露的sendHtmlWithTemplate
這個方法裡面就不需要再重啟一個線程異步去發送郵件,因為對於調用方,其實你需要告訴它這次發送郵件是否成功,如果失敗了/異常了,失敗的原因是什麼。如果按照你現有的方案的話,可能給外面感覺就是,不管郵件發送成功或者失敗,外面都是沒有感知的,意味著不能將這封郵件持久化,並且過一段時間再重試。所以非同步發送郵件應該交給你的呼叫方,而不是在你的方法裡面假設我們的發送郵件可能是一個很耗時的動作,那麼按照每來一封郵件就啟一個線程去做的話,意味這到時候系統裡面會產生很多線程。那麼就意味會有很多線程切換,線程的切換,線程的切換就會有阻塞和喚醒,這些都涉及到用戶態和內核態的轉換或者是線程的上下文的切換,都是很耗CPU的,極端情況如果系統線程不段堆積的話,會導致整個服務DOWN機,服務處於不可用的狀態。 new Thread()確實是個很昂貴的操作
如果正確的做非同步發送郵件,確實需要放到線程池裡面做
更加的好的方法,一種就是透過MQ來發送郵件訊息(考慮訊息常見蛋疼的問題1.訊息是否會遺失2.訊息重發了怎麼辦),單獨交給一台發送郵件的伺服器去做。還有一種就是先存入資料庫表,如果時效性要求不是很高的話,到時候啟一個定時器慢慢發。
匿名物件很方便,但匿名物件也是魔鬼,使用不好就能讓你的應用程式崩塌。就拿你這裡做例子,如果你沒有控制好發送郵件的參數,比如發送時超時了就繼續等待,那這個線程可就變成了孤魂野鬼了,找也找不到,關也關不掉,這是程式設計師該做的事情嗎?
你問為何你的頭讓你把這裡去掉,我只能說是他還不夠相信的你能力,所以不敢讓你操縱這個魔鬼。
你問什麼場景下適合,我只能說沒有適合的場景,只有有能力的人。
如果要發大量的郵件,可以做個佇列,線程太多的話,恐怕不太好。
不好意思,現在才看到問題。
具體參考@iMouseWu 和 @有明 的答案。
如果你有寫過js,你可以理解這裡的
new Thread()
相当于ajax
請求,是屬於異步行為,在這個線程任務還沒執行完之前,方法已經返回給調用方了(而且不會拋出發送郵件產生的異常) ,這也就是你leader說的「這些郵件發送不了的bug都沒有暴露出來」。考慮並發量大的話可以採用
redis
+kafka
(或其他訊息元件),用訊息的形式去通知郵件服務發送郵件。