题 “实现Runnable”与“扩展线程”


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

implements Runnable

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

或者,与 extends Thread

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

这两个代码块有什么显着差异吗?


1797
2018-02-12 14:28


起源


感谢您提出这个问题,答案消除了我的许多误解。在SO存在之前,我研究了正确的Java线程方式,并且有很多错误的信息/过时的信息。 - James McMahon
有一个原因你可能想要扩展Thread(但我不推荐它),你可以先发制人地处理 interrupt()。同样,这是一个想法,它可能在正确的情况下有用,但我不推荐它。 - bestsss
请参阅答案,很好地解释: stackoverflow.com/q/5562720/285594 - YumYumYum
@bestsss,我试图弄清楚你对处理interrupt()的意思。你想覆盖这个方法吗? - Bob Cross
是的。根据代码,类Thread A可以扩展任何类,而类Thread B不能扩展任何其他类 - mani deepak


答案:


是的:实施 Runnable 是IMO的首选方式。你并不是真正专注于线程的行为。你只是给它一些东西来运行。这意味着 组成 是个 哲学 “更纯粹”的方式去。

实际的 条款,这意味着你可以实施 Runnable 并从另一个类扩展。


1447
2018-02-12 14:32



确实,好的。我们试图通过扩展它在Thread中覆盖什么行为?我认为大多数人都不会试图覆盖任何行为,而是试图使用Thread的行为。 - hooknc
作为旁注,如果你实例化一个Thread并且不调用它的start()方法,你就会在Java <5中创建一个内存泄漏(Runnables不会发生这种情况): stackoverflow.com/questions/107823/... - Nacho Coloma
Runnable的一个小优点是,如果在某些情况下你不关心,或者不想使用线程,并且你只想执行代码,你可以选择简单地调用run()。例如(非常手绘) if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run() - user949300
@ user949300你也可以这样做 extends Thread 如果你不想要线程,你为什么要实现呢? Runnable... - m0skit0
要解释Sierra和Bates,实施Runnable的一个主要好处是,你在建筑上将“工作”与“跑步者”分开。 - 8bitjunkie


tl; dr:实现Runnable更好。但是,警告很重要

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

警告: 在这里,我强烈反对使用原始线程。我更喜欢使用 可调用 和 FutureTasks (来自javadoc:“可取消的异步计算”)。超时,正确取消和现代并发支持的线程池的集成对我来说比成堆的原始线程更有用。

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

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

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

所以,如果我们更换他们的 runnable 和你的 threadA,我们得到以下内容:

new FutureTask<Object>(threadA, null)

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

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

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

494
2018-02-12 14:37



这比接受的答案恕我直言。有一件事:你拥有的代码片段并没有关闭执行者,我看到数百万个问题,人们错了,每当他们想要生成任务时创建一个新的Executor。 es作为静态(或注入)字段会更好,所以它只创建一次。 - artbristol
@artbristol,谢谢!我对新的Executor没有异议(我们在你的代码中做了你的建议)。在写原始答案时,我试图编写与原始片段类似的最小代码。我们不得不希望这些答案的许多读者将它们作为跳跃点。我不是要为javadoc写一个替代品。我正在为它有效地编写营销材料: 如果你喜欢这种方法,你应该看到我们提供的所有其他伟大的东西......! - Bob Cross
我知道我对此发表评论的时间有点迟了,但是要处理 FutureTask 直接通常不是你想要做的。 ExecutorServices将创建合适的 Future 当你的时候 submit 一个 Runnable/Callable 给他们。同样地 ScheduledExecutorServices和 ScheduledFuture 当你 schedule 一个 Runnable/Callable。 - Powerlord
@Powerlord,我的目的是尽可能地制作与OP匹配的代码片段。我同意新的FutureTask并不是最优的,但出于解释的目的这一点很清楚。 - Bob Cross


故事的道德启示:

仅在您要覆盖某些行为时继承。

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

继承少,界面更多。


226
2018-03-11 15:50



如果您开始创建并发运行的对象,这应该始终是个问题!你甚至需要Thread Object功能吗? - Liebertee
从Thread继承时,几乎总是想要覆盖的行为 run() 方法。 - Warren Dew
你无法覆盖一个人的行为 java.lang.Thread 通过压倒 run() 方法。在这种情况下,你需要覆盖 start() 方法我猜。通常你只是重用了行为 java.lang.Thread 通过将执行块注入到 run() 方法。 - sura2k


那么多好的答案,我想在此添加更多。这有助于理解 Extending v/s Implementing Thread
Extends非常紧密地绑定两个类文件,并且可能导致一些非常难以处理的代码。

两种方法都做同样的工作,但存在一些差异。
最常见的区别是 

  1. 当您扩展Thread类时,之后您无法扩展您需要的任何其他类。 (如您所知,Java不允许继承多个类)。
  2. 实现Runnable时,可以为类保存一个空间,以便将来或现在扩展任何其他类。

但是,一个 显着差异 在实现Runnable和扩展Thread之间是这样的
  by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instance.

以下示例可帮助您更清楚地理解

//Implement Runnable Interface...
 class ImplementsRunnable implements Runnable {

private int counter = 0;

public void run() {
    counter++;
    System.out.println("ImplementsRunnable : Counter : " + counter);
 }
}

//Extend Thread class...
class ExtendsThread extends Thread {

private int counter = 0;

public void run() {
    counter++;
    System.out.println("ExtendsThread : Counter : " + counter);
 }
}

//Use above classes here in main to understand the differences more clearly...
public class ThreadVsRunnable {

public static void main(String args[]) throws Exception {
    // Multiple threads share the same object.
    ImplementsRunnable rc = new ImplementsRunnable();
    Thread t1 = new Thread(rc);
    t1.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    Thread t2 = new Thread(rc);
    t2.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    Thread t3 = new Thread(rc);
    t3.start();

    // Creating new instance for every thread access.
    ExtendsThread tc1 = new ExtendsThread();
    tc1.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    ExtendsThread tc2 = new ExtendsThread();
    tc2.start();
    Thread.sleep(1000); // Waiting for 1 second before starting next thread
    ExtendsThread tc3 = new ExtendsThread();
    tc3.start();
 }
}

输出上述程序。

ImplementsRunnable : Counter : 1
ImplementsRunnable : Counter : 2
ImplementsRunnable : Counter : 3
ExtendsThread : Counter : 1
ExtendsThread : Counter : 1
ExtendsThread : Counter : 1

在Runnable接口方法中,只创建了一个类的一个实例,并且它已由不同的线程共享。因此,对于每个线程访问,计数器的值都会递增。

而Thread类方法必须为每个线程访问创建单独的实例。因此,为每个类实例分配不同的内存,每个内存都有单独的计数器,值保持相同,这意味着不会发生任何增量,因为没有任何对象引用是相同的。

什么时候使用Runnable?
如果要从线程组访问相同的资源,请使用Runnable接口。避免在这里使用Thread类,因为多个对象创建会占用更多内存,并且会成为很大的性能开销。

实现Runnable的类不是一个线程而只是一个类。要使Runnable成为线程,您需要创建一个Thread实例并将其自身作为目标传递。

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

当需要扩展超类时,实现Runnable接口比使用Thread类更合适。因为我们可以在实现Runnable接口的同时扩展另一个类来创建一个线程。

我希望这个能帮上忙!


185
2018-03-05 14:26



你的代码显然是错误的。我的意思是,它做它做的,但不是你打算展示的。 - zEro
为了澄清:对于可运行的情况,您使用相同的ImplementsRunnable实例来启动多个线程,而对于Thread情况,您创建了不同的ExtendsThread实例,这显然会导致您显示的行为。主方法的下半部分应该是: ExtendsThread et = new ExtendsThread();  Thread tc1 = new Thread(et);  tc1.start();  Thread.sleep(1000);  Thread tc2 = new Thread(et);  tc2.start();  Thread.sleep(1000);  Thread tc3 = new Thread(et);  tc3.start();  它更清楚吗? - zEro
我还不了解你的意图,但我的观点是,如果你创建了多个ExtendsThread实例 - 它们都将返回1(如你所示)。你可以通过在那里做同样的事情来获得Runnable的相同结果,即创建ImplementsRunnable的多个实例。 - zEro
@zEro嗨,我来自未来。鉴于您的代码版本具有 Thread 声明也是递增的 by extending Thread, each of your threads has a unique object associated with it, whereas implementing Runnable, many threads can share the same object instance 然后错了?如果没有,那么这是什么案例呢? - Evil Washing Machine
@EvilWashingMachine:长时间不活动......只是看到了这个。我已将对象的哈希码添加到print语句中... + " hashcode: " + this.hashCode() - zEro


我还没有提到的一件事就是实施 Runnable 让你的课程更灵活。

如果你扩展线程,那么你正在做的动作总是在一个线程中。但是,如果你实施 Runnable 它不一定是。您可以在一个线程中运行它,或者将它传递给某种执行器服务,或者只是作为单个线程应用程序中的任务传递它(可能在以后运行,但在同一个线程内)。如果你只是使用,选项会更开放 Runnable 而不是你自己约束自己 Thread


73
2018-02-12 14:51



嗯,你实际上可以做一个相同的事情 Thread 对象也是因为 Thread implements Runnable...... ;-)但用“a”做这件事“感觉更好” Runnable 而不是用它来做 Thread! - siegi
没错,但是 Thread 添加了许多您不需要的额外内容,并且在许多情况下不需要。你总是更好地实现与你实际做的匹配的界面。 - Herms


如果你想实现或扩展任何其他类 Runnable 如果您不希望任何其他类扩展或实现,那么接口是最优选的 Thread 上课是可取的

最常见的区别是

enter image description here

当你 extends Thread 在此之后,您无法扩展您需要的任何其他课程。 (如您所知,Java不允许继承多个类)。

当你 implements Runnable,您可以为您的班级节省空间,以便将来或现在扩展任何其他课程。

  • Java不支持多重继承,这意味着你只能在Java中扩展一个类,所以一旦你扩展了Thread类,你就失去了机会,无法在Java中扩展或继承另一个类。

  • 在面向对象的编程中,扩展类通常意味着添加新功能,修改或改进行为。如果我们不在Thread上进行任何修改,那么请改用Runnable接口。

  • Runnable接口表示一个Task,它可以由普通的Thread或Executor或任何其他方法执行。所以将Task作为Runnable与Thread进行逻辑分离是一个很好的设计决策。

  • 将任务分离为Runnable意味着我们可以重用该任务,并且可以自由地从不同的方式执行它。因为一旦完成就无法重启线程。再次Runnable vs Thread for task,Runnable是胜利者。

  • Java设计者认识到这一点,这就是Executors接受Runnable作为Task的原因,他们有工作线程来执行这些任务。

  • 继承所有Thread方法只是用于表示可以使用Runnable轻松完成的Task的额外开销。

礼貌来自 javarevisited.blogspot.com

这些是Java中Thread和Runnable之间的一些显着差异,如果你知道Thread vs Runnable上的任何其他差异,请通过评论分享。我个人在这种情况下使用Runnable over Thread,并建议根据您的要求使用Runnable或Callable接口。

但是,显着的区别是。

当你 extends Thread class,每个线程创建唯一对象并与之关联。 当你 implements Runnable,它与多个线程共享同一个对象。


65
2018-05-11 08:59





实际上,比较并不明智 Runnable 和 Thread 彼此。

这两者在多线程中具有依赖关系和关系 Wheel and Engine 机动车的关系。

我想说,只有一种方法可以通过两个步骤实现多线程。让我说明我的观点。

可运行:
实施时 interface Runnable 这意味着你正在创造一些东西 run able 在另一个线程中。现在创建可以在线程内运行的东西(在线程内可运行)并不意味着创建一个Thread。
所以上课 MyRunnable 只不过是一个普通的班级 void run 方法。 它的对象将是一些只有一个方法的普通对象 run 它会在被调用时正常执行。 (除非我们在一个线程中传递对象)。

线:
class Thread,我会说一个非常特殊的类,具有启动新线程的能力,实际上可以通过它实现多线程 start() 方法。

为什么不明智地比较?
因为我们需要它们用于多线程。

对于多线程,我们需要两件事:

  • 可以在Thread(Runnable)中运行的东西。
  • 可以启动新线程(线程)的东西。

从技术上和理论上来说,两者都是开始一个线程所必需的  一个会 让它运行 (喜欢 Wheel and Engine 汽车)。

这就是你无法启动线程的原因 MyRunnable 你需要将它传递给一个实例 Thread

 可以仅使用创建和运行线程 class Thread 因为上课 Thread 器物 Runnable 所以我们都知道 Thread 也是一个 Runnable 内。

最后 Thread 和 Runnable 对于多线程而不是竞争对手或替代品,它们是相互补充的。


61
2018-05-12 13:50



究竟!这应该是公认的答案。顺便说一下,我认为这个问题已被编辑过 ThreadA不再有意义 - idelvall
接受的答案是更多代表,谢谢你回复@idelvall - Saif


您应该实现Runnable,但如果您在Java 5或更高版本上运行,则不应该启动它 new Thread 但是用一个 ExecutorService的 代替。详情见: 如何在Java中实现简单的线程


41
2018-02-12 14:41



如果您只想启动单个线程,我不认为ExecutorService会有用。 - Powerlord
从我所学到的,一般不应该自己启动一个线程,因为将它留给执行者服务使得所有更可控(例如,等待线程暂停)。此外,我没有在问题中看到任何暗示它是关于单个线程的内容。 - Fabian Steeg
如果我们知道它将是一个单线程,那么使用任何多线程的重点是什么。所以我们假设我们有多个线程,这个答案很有价值。 - zEro
@zEro我很确定只有一个Event Dispatch Thread是有原因的。我怀疑这是唯一的情况是最好有一个单独的线程,但可能不是最好有多个。 - porcoesphino


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

编辑:这最初说“实现一个接口需要更少的资源。”同样,但你需要创建一个新的Thread实例,所以这是错误的。


31
2018-02-12 14:32



在runnable中我们无法进行网络通话,是吗?因为我有android.os.NetworkOnMainThreadException。但通过使用线程我可以进行网络调用。如果我错了,请纠正我。 - Nabeel Thobani
@NabeelThobani普通Java并不关心,但听起来像Android。不过,我对Android不太熟悉。 - Powerlord
@NabeelThobani当然可以。可能你没有用Runnable创建一个Thread。 - m0skit0


我想说还有第三种方式:

public class Something {

    public void justAnotherMethod() { ... }

}

new Thread(new Runnable() {
   public void run() {
    instanceOfSomething.justAnotherMethod();
   }
}).start();

也许这有点受到我最近大量使用Javascript和Actionscript 3的影响,但是这样你的类不需要实现一个非常模糊的界面 Runnable


19
2017-10-25 21:41



这不是第三种方式。您仍在实施Runnable,只是匿名进行。 - Don Roby
@Don Roby:哪个不同。它通常很方便,您可以使用包含类/方法中的字段和最终局部变量。 - Bart van Heukelom
是的,很方便。 - Don Roby
@BartvanHeukelom这很方便,但并没有什么不同。您可以使用任何类型的嵌套类(即内部类,本地类和lambda表达式)执行此操作。 - xehpuk