我得到了这个作为面试问题。
为什么线程类不是最终的?你为什么要扩展一个线程呢?
我想不出现实世界的用例。
来自Oracle 的文档:
有两种方法可以创建一个新的执行线程。一种是将类声明为 Thread 的子类。这个子类应该重写类 Thread 的 run 方法。创建线程的另一种方法是声明一个实现 Runnable 接口的类。
所以答案是“你可能想要子类化Thread
来覆盖它的run()
方法”。
引用的段落可以追溯到 JDK 1.1 的 Java 文档中。Java 添加了其他方便的类来管理并发,最值得注意的是评论中提到的执行器,可能会减少或消除扩展的需要Thread
。然而,他们无法做到这一点final
,因为这会破坏向后兼容性。
就实际原因而言,我认为您今天可能想要扩展Thread
而不是实现的唯一原因是覆盖它的方法,而不是. 例如,您可能想要添加日志记录或额外的清理。Runnable
run()
这几乎只是取自 John Vint 的评论,但我认为这是最好的答案。
我唯一能想到可以扩展Thread
而不是实现的地方Runnable
——或者更好的是,只使用ExecutorService
带有 a 的Future
——是当我需要重写Thread.interrupt()
以进行一些清理时。否则,我看不到任何实际扩展的实际理由Thread
。
让我这样说:在设计一种语言时,它是一种与其他任何工具一样的工具,应该考虑利弊,而不是仅仅限制工具,因为我们无法看到适用的真实用例。通过这样做,我们能够从理论上理解这些决定。基本上,这个练习是反其道而行之。
为什么线程应该是最终的?
让我进入正题。
(我不包括向后兼容性的论点,就我个人而言,我不喜欢它,因为我认为我们应该始终专注于本地改进)
为了能够讨论这样的主题,我们需要了解将一个类声明为 final 与否的好处。
这里 TofuBeer说:
虚拟(覆盖)方法通常是通过某种表(vtable)实现的,该表最终是一个函数指针。每个方法调用都有必须通过该指针的开销。当类被标记为 final 时,所有方法都不能被覆盖,并且不再需要使用表 - 这更快。
一些虚拟机(如 HotSpot)可能会更智能地做事,并且知道方法何时/未被覆盖,并酌情生成更快的代码。
这里oracle 亮点:
最好在设计 API 时考虑到安全性。尝试在现有 API 中改进安全性更加困难且容易出错。例如,将类设为 final 可防止恶意子类添加终结器、克隆和覆盖随机方法。
不变量和敏感信息也存在问题。但是,所有安全问题都是项目特定的,不适用于可用于非安全问题场景的工具。
andersoj在这里提到了这篇不错的 IBM 文章:
final 类和方法在编程时可能会给您带来很大的不便——它们限制了您重用现有代码和扩展现有类的功能的选择。虽然有时一个类被定为 final 是有充分理由的,例如强制不变性,但使用 final 的好处应该超过不便之处。性能增强几乎总是损害良好的面向对象设计原则的一个不好的理由,当性能增强很小或不存在时,这确实是一个不好的权衡。
因此,从我的角度来看,如果将类设置为 final 没有很大的好处,如果 JVM 能够进行这样的性能优化,我会说方便的论点获胜。
因此,我们可以扩展 Thread 类并用它做任何我们想做的事情,一些初始化/终结的东西,比如日志记录、更改线程名称、线程类型......这取决于你!
两种情况:
Thread
,也许是在完成后清理一些资源等run()
方法,而不是向Runnable
构造函数提供 a (注意:避免这种模式 - 这不是正确的方法)另一个Thread
不是最终的原因是,在 Java 的早期,覆盖run()
被认为是一种很好的设计模式。(我想,在匿名类之前的日子里,人们认为子类比Thread
创建一个实现 . 的独立类更“整洁” Runnable
。)
无论如何,一旦 Java 1.0 发布,因为不可能通过更改Thread
为最终版本来解决问题。那会破坏很多现有的代码。
它可能是在派生Thread
类中保存线程局部变量的字段。run()
当方法没有被覆盖并且线程Runnable
像往常一样执行时,这些变量也可以访问。这样的自定义线程可以由标准管理,ExecutorService
在自定义中创建它们ThreadFactory
并根据需要丢弃。
我知道还有ThreadLocal
,但是如果字段需要清理(比如它们是数据库连接),这可以通过调用super.run()
processRunnable
并在方法返回之前立即完成run
(因此线程终止)来完成。