从我在Java中使用线程的时间来看,我发现了两种编写线程的方法:

使用可运行的机具:

public class MyRunnable implements Runnable {
    public void run() {
        //Code
    }
}
//Started with a "new Thread(new MyRunnable()).start()" call

或者,使用扩展线程:

public class MyThread extends Thread {
    public MyThread() {
        super("MyThread");
    }
    public void run() {
        //Code
    }
}
//Started with a "new MyThread().start()" call

这两个代码块有什么显著的区别吗?


当前回答

故事的寓意:

仅当您想要覆盖某些行为时才继承。

或者更确切地说,它应该被理解为:

继承更少,接口更多。

其他回答

tl;dr:implements Runnable更好。然而,警告很重要。

一般来说,我建议使用Runnable而不是Thread这样的工具,因为它允许您保持工作与并发选择之间的松散耦合。例如,如果您使用了一个Runnable,并且稍后决定它实际上不需要自己的Thread,那么您可以调用threadA.run()。

注意:在这里,我强烈反对使用原始线程。我更喜欢使用Callables和FutureTasks(来自javadoc:“可取消的异步计算”)。现代并发支持的超时、适当取消和线程池的集成对我来说都比成堆的原始线程有用得多。

后续:有一个FutureTask构造函数,它允许您使用Runnables(如果这是您最熟悉的),并且仍然可以获得现代并发工具的好处。引用javadoc:

如果不需要特定的结果,请考虑使用以下形式的构造:

Future<?> f = new FutureTask<Object>(runnable, null)

因此,如果我们用threadA替换它们的runable,我们会得到以下结果:

new FutureTask<Object>(threadA, null)

另一个让您更接近Runnables的选项是ThreadPoolExecutor。您可以使用execute方法传入Runnable以执行“将来某个时候的给定任务”。

如果您想尝试使用线程池,上面的代码片段将变成如下(使用Executors.newCachedThreadPool()工厂方法):

ExecutorService es = Executors.newCachedThreadPool();
es.execute(new ThreadA());

由于这是一个非常流行的话题,而且好的答案到处都是,而且处理得非常深入,所以我觉得将其他人的好答案汇编成更简洁的形式是合理的,因此新来者可以很容易地概括:

通常扩展类以添加或修改功能。因此,如果不想覆盖任何线程行为,请使用Runnable。同样,如果不需要继承线程方法,那么可以通过使用Runnable来避免这种开销。单一继承:如果您扩展Thread,则不能从任何其他类扩展,因此如果这是您需要做的,则必须使用Runnable。将领域逻辑与技术手段分开是一个很好的设计,从这个意义上说,最好有一个可运行的任务将您的任务与运行者隔离开来。您可以多次执行同一个Runnable对象,但Thread对象只能启动一次。(也许是执行器接受Runnables而不接受Threads的原因。)如果您将任务开发为Runnable,那么您现在和将来都可以灵活使用它。您可以通过执行器同时运行,也可以通过线程运行。您还可以在同一线程中非并发地使用/调用它,就像其他任何普通类型/对象一样。这也使得在单元测试中分离任务逻辑和并发方面更加容易。如果你对这个问题感兴趣,你可能也会对Callable和Runnable之间的区别感兴趣。

实例化接口在代码和线程的实现之间提供了更清晰的分离,因此在这种情况下我更希望实现Runnable。

我不是专家,但我能想到实现Runnable而不是扩展Thread的一个原因:Java只支持单一继承,因此只能扩展一个类。

编辑:这原本是说“实现一个接口需要更少的资源”,但无论如何都需要创建一个新的线程实例,所以这是错误的。

实现Runnable和扩展Thread之间的一个区别是,通过扩展Thread,每个线程都有一个与其关联的唯一对象,而实现Runnable,许多线程可以共享同一个对象实例。

实现Runnable的类不是线程,只是一个类。对于要由线程执行的Runnable,需要创建一个Thread实例,并将Runnable实例作为目标传入。

在大多数情况下,如果只打算重写run()方法而不打算重写其他Thread方法,则应使用Runnable接口。这一点很重要,因为除非程序员打算修改或增强类的基本行为,否则类不应该被子类化。

当需要扩展超类时,实现Runnable接口比使用Thread类更合适。因为我们可以在实现Runnable接口时扩展另一个类以生成线程。但是如果我们只是扩展Thread类,我们就不能从任何其他类继承。