Java 中的一个常见建议是在构造对象期间不要让“this”引用逃逸,因此不要在构造函数中启动任何线程。但是我发现自己编写了很多应该使用执行程序启动线程的类。根据一般建议,我应该编写一个额外的 start() 方法,将任务提交给执行程序。
但是将任务提交给执行程序会提供发生在此处的保证。那么在构造函数的最后一条语句中提交任务会好吗?或者更普遍的问题:如果该语句提供了先发生条件的保证,那么在构造函数的最后一个语句中让“this”转义是否安全?
是的,这是安全的,因为提供happens-before 保证的语句将确保所有字段都正确初始化对其他线程可见。一个警告是,子类可能会破坏这种安全性,因此最好将类设为 final。但是,正如 Holger 指出的那样,即使是额外的构造函数委托给启动线程的构造函数也可能会危害安全。
一般建议“不要让这个从构造函数中逃脱”的存在主要是因为遵循此规则更容易,因此更不容易出错,然后记住所有细微差别(如子类化)。
Stefan Feuerhahn的回答是正确的。
我将添加一个建议,即在执行工作的类中嵌入 executor 服务可能是“代码异味”,这是弱设计的迹象。
通常我们希望在我们的设计中遵循单一职责原则。一个类应该有一个单一的目的,并且应该尽量不偏离那个狭隘的特定目的。
例如,如果编写一个类来创建报告,则该类应该只知道该报告。该类不应该知道应该何时运行该报告,或者运行报告的频率,或者如果报告已经运行,其他代码关心什么。
这种何时运行报告的计划与应用程序的生命周期相关。一件重要的事情是,当不再需要或应用程序退出时,最终必须关闭 executor 服务。否则,后备线程池可能会像僵尸一样无限期地继续下去。您的报告生成类不应该知道何时不再需要它,也不应该知道应用程序何时或为什么退出。
问题的另一方面是配置执行程序服务涉及了解部署场景。多少 RAM、多少 CPU 内核、主机上有多少其他负担,都有助于决定如何设置执行程序服务。您的报告生成代码不应因部署情况的变化而改变。
报告生成类不应该了解调用应用程序的生命周期,也不应该了解执行程序服务。生成报告的应用程序应该只知道如何生成一份报告。代码中的其他地方,可能是某个报表管理器类或应用程序的生命周期编排代码,应该处理运行报表的频率和时间。