6

我得到了这个作为面试问题。

为什么线程类不是最终的?你为什么要扩展一个线程呢?

我想不出现实世界的用例。

4

6 回答 6

10

来自Oracle 的文档

有两种方法可以创建一个新的执行线程。一种是将类声明为 Thread 的子类。这个子类应该重写类 Thread 的 run 方法。创建线程的另一种方法是声明一个实现 Runnable 接口的类。

所以答案是“你可能想要子类化Thread来覆盖它的run()方法”。

引用的段落可以追溯到 JDK 1.1 的 Java 文档中。Java 添加了其他方便的类来管理并发,最值得注意的是评论中提到的执行器,可能会减少或消除扩展的需要Thread。然而,他们无法做到这一点final,因为这会破坏向后兼容性。

实际原因而言,我认为您今天可能想要扩展Thread而不是实现的唯一原因是覆盖它的方法,而不是. 例如,您可能想要添加日志记录或额外的清理。Runnable run()

于 2012-05-02T14:17:42.020 回答
2

这几乎只是取自 John Vint 的评论,但我认为这是最好的答案。

我唯一能想到可以扩展Thread而不是实现的地方Runnable——或者更好的是,只使用ExecutorService带有 a 的Future——是当我需要重写Thread.interrupt()以进行一些清理时。否则,我看不到任何实际扩展的实际理由Thread

于 2012-05-02T14:34:01.137 回答
1

让我这样说:在设计一种语言时,它是一种与其他任何工具一样的工具,应该考虑利弊,而不是仅仅限制工具,因为我们无法看到适用的真实用例。通过这样做,我们能够从理论上理解这些决定。基本上,这个练习是反其道而行之。

为什么线程应该是最终的?

让我进入正题。


我不包括向后兼容性的论点,就我个人而言,我不喜欢它,因为我认为我们应该始终专注于本地改进

为了能够讨论这样的主题,我们需要了解将一个类声明为 final 与否的好处。

表现

这里 TofuBeer说:

虚拟(覆盖)方法通常是通过某种表(vtable)实现的,该表最终是一个函数指针。每个方法调用都有必须通过该指针的开销。当类被标记为 final 时,所有方法都不能被覆盖,并且不再需要使用表 - 这更快。

一些虚拟机(如 HotSpot)可能会更智能地做事,并且知道方法何时/未被覆盖,并酌情生成更快的代码。

安全

这里oracle 亮点:

最好在设计 API 时考虑到安全性。尝试在现有 API 中改进安全性更加困难且容易出错。例如,将类设为 final 可防止恶意子类添加终结器、克隆和覆盖随机方法。

不变量和敏感信息存在问题。但是,所有安全问题都是项目特定的,不适用于可用于非安全问题场景的工具。

方便

andersoj在这里提到了这篇不错的 IBM 文章:

final 类和方法在编程时可能会给您带来很大的不便——它们限制了您重用现有代码和扩展现有类的功能的选择。虽然有时一个类被定为 final 是有充分理由的,例如强制不变性,但使用 final 的好处应该超过不便之处。性能增强几乎总是损害良好的面向对象设计原则的一个不好的理由,当性能增强很小或不存在时,这确实是一个不好的权衡。

因此,从我的角度来看,如果将类设置为 final 没有很大的好处,如果 JVM 能够进行这样的性能优化,我会说方便的论点获胜。

因此,我们可以扩展 Thread 类并用它做任何我们想做的事情,一些初始化/终结的东西,比如日志记录、更改线程名称、线程类型......这取决于你!

于 2014-04-11T11:14:44.233 回答
1

两种情况:

  1. 创建一种新的Thread,也许是在完成后清理一些资源等
  2. 覆盖该run()方法,而不是向Runnable构造函数提供 a (注意:避免这种模式 - 这不是正确的方法)
于 2012-05-02T14:18:11.417 回答
1

另一个Thread不是最终的原因是,在 Java 的早期,覆盖run()被认为是一种很好的设计模式。(我想,在匿名类之前的日子里,人们认为子类比Thread创建一个实现 . 的独立类更“整洁” Runnable。)

无论如何,一旦 Java 1.0 发布,因为不可能通过更改Thread为最终版本来解决问题。那会破坏很多现有的代码。

于 2012-05-02T14:44:15.310 回答
0

它可能是在派生Thread类中保存线程局部变量的字段。run()当方法没有被覆盖并且线程Runnable像往常一样执行时,这些变量也可以访问。这样的自定义线程可以由标准管理,ExecutorService在自定义中创建它们ThreadFactory并根据需要丢弃。

我知道还有ThreadLocal,但是如果字段需要清理(比如它们是数据库连接),这可以通过调用super.run()processRunnable并在方法返回之前立即完成run(因此线程终止)来完成。

于 2017-08-23T20:06:42.547 回答