提问



从我在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


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

最佳参考


是的:实现Runnable是首选方式,IMO。你并没有真正专注于线程的行为。你只是给它一些东西来运行。这意味着构图是哲学更纯粹的方式。[106]


在实用术语中,它意味着您可以实现Runnable并从另一个类扩展。

其它参考1


tl; dr:实现Runnable更好。但是,需要注意的是


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


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


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


如果您不需要特定结果,请考虑使用表单的结构:


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


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


new FutureTask<Object>(threadA, null)


允许您更接近Runnables的另一个选项是ThreadPoolExecutor。您可以使用execute方法传入Runnable以在将来某个时间执行给定任务。[110] [111]


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


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

其它参考2


故事的道德启示:


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


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


继承更少,界面更多。

其它参考3


那么多好的答案,我想在此添加更多。这将有助于理解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实例并将其自身作为目标传递。


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


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


我希望这个能帮上忙!

其它参考4


令我感到惊讶的一件事还没有提到,实施Runnable会让你的课更灵活。


如果你扩展线程,那么你所做的动作总是在一个线程中。但是,如果你实现Runnable,它就不一定是。您可以在一个线程中运行它,或者将它传递给某种执行器服务,或者只是作为单个线程应用程序中的任务传递它(可能在以后运行,但在同一个线程内)。如果你只使用Runnable,那么这些选项要比将你自己绑定到Thread要多得多。

其它参考5


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


最常见的区别是





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


当你implements Runnable时,你可以为你的班级保存一个空间,以便将来或现在扩展任何其他课程。



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

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

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

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

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

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



礼貌来自javarevisited.blogspot.com [113]


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


然而,显着的区别是。


当你extends Thread类时,你的每个线程都会创建唯一的对象并与之关联。
当你implements Runnable时,它将同一个对象共享给多个线程。

其它参考6


实际上,将RunnableThread相互比较是不明智的。


这两者在多线程中具有依赖性和关系,就像机动车的Wheel and Engine关系一样。


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


可运行:结果
实现interface Runnable时,意味着您在另一个线程中创建run able的东西。现在创建一个可以在一个线程内运行的东西(在一个线程内可运行),并不意味着创建一个Thread。

所以类MyRunnable只不过是一个带void run方法的普通类。
它的对象将是一些普通的对象,只有一个方法run,它会在被调用时正常执行。(除非我们在一个线程中传递对象)。


主题:结果
class Thread,我想说一个非常特殊的类,它具有启动一个新线程的能力,它实际上通过其start()方法启用了多线程。


为什么不明智地比较?

因为我们需要它们用于多线程。


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



  • 可以在Thread(Runnable)中运行的东西。

  • 可以启动新线程(线程)的东西。



因此从技术上和理论上讲,两者都是启动线程所必需的,一个将运行一个将使其运行(如 Wheel and Engine 机动车辆)。


这就是为什么你不能用MyRunnable启动一个线程,你需要将它传递给Thread的实例。


但是可以仅使用class Thread创建和运行一个线程,因为Class Thread实现Runnable所以我们都知道Thread也是一个[[Runnable里面。


最后ThreadRunnable是多线程而非竞争对手或替代者的补充。

其它参考7


您应该实现Runnable,但是如果您在Java 5或更高版本上运行,则不应该使用new Thread启动它,而是使用ExecutorService。有关详细信息,请参阅:如何在Java中实现简单线程。[114]

其它参考8


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


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

其它参考9


我想说还有第三种方式:


public class Something {

    public void justAnotherMethod() { ... }

}

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


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

其它参考10


随着Java 8的发布,现在有第三种选择。


Runnable是一个功能接口,这意味着可以使用lambda表达式或方法引用创建它的实例。[116]


您的示例可以替换为:


new Thread(() -> { /* Code here */ }).start()


或者如果你想使用ExecutorService和方法参考:


executor.execute(runner::run)


这些不仅比你的例子短得多,而且还具有使用Runnable而不是Thread的其他答案中所述的许多优点,例如单一责任和使用构图,因为你没有专注于线程的行为。如果您需要的是Runnable,这样也可以避免创建额外的类,就像您在示例中一样。

其它参考11


实例化接口可以在代码和线程实现之间实现更清晰的分离,因此我更倾向于在这种情况下实现Runnable。

其它参考12



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

  2. 在面向对象的编程中,扩展类通常意味着添加新功能,修改或改进行为。如果我们不在Thread上进行任何修改,而不是使用Runnable界面。

  3. Runnable接口表示Task,可以通过普通ThreadExecutors或任何其他方式执行。因此TaskRunnable的逻辑分离比Thread是良好的设计决策。

  4. 将任务分离为Runnable意味着我们可以重用任务,并且可以自由地从不同的方式执行它。由于你Thread一旦完成就无法重新启动,Runnable vs Thread的任务,Runnable是胜利者。

  5. Java设计师认识到这一点,这就是为什么Executors接受RunnableTask并且他们有工作线程来执行这些任务。

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


其它参考13


Runnable因为:



  • 为此留下更多灵活性
    Runnable实现扩展
    另一个班级

  • 将代码分开
    执行

  • 允许您运行您的
    可以从线程池中运行
    事件线程,或以任何其他方式
    未来。



即使你现在不需要这些,也可能在未来。由于重写Thread没有任何好处,Runnable是一个更好的解决方案。

其它参考14


这里的每个人似乎都认为实现Runnable是我要走的路,我并不是真的不同意他们,但我认为还有一个扩展Thread的案例,事实上你已经在你的代码中展示了它。


如果实现Runnable,那么实现Runnable的类无法控制线程名称,它是可以设置线程名称的调用代码,如下所示:


new Thread(myRunnable,"WhateverNameiFeelLike");


但是如果你扩展Thread然后你就可以在类本身内管理它(就像在你的例子中你命名线程ThreadB)。在这种情况下你:


A)可能会为调试目的提供一个更有用的名称


B)强制该名称用于该类的所有实例(除非你忽略它是一个线程并使用它执行上面的操作,就好像它是一个Runnable但我们在这里谈论约定,所以可以忽略我觉得的那种可能性)。


您甚至可以例如获取其创建的堆栈跟踪并将其用作线程名称。这可能看起来很奇怪,但根据代码的结构,它对调试非常有用。


这可能看起来像一个小东西,但是你有一个非常复杂的应用程序,有很多线程,突然之间已经停止(出于死锁的原因,或者可能是因为网络协议中的一个缺陷会更少)明显 - 或其他无穷无尽的原因)然后从Java获取堆栈转储,其中所有线程被称为线程-1,线程-2,线程-3并不总是非常有用(它取决于您的线程是如何结构化以及是否可以通过堆栈跟踪有用地告诉哪个是哪个 - 如果您使用的是多个线程的组都运行相同的代码,则不总是可行的。


说过你当然也可以通过创建一个线程类的扩展来以一般方式完成上述操作,该线程类将其名称设置为其创建调用的堆栈跟踪,然后将其与Runnable实现而不是标准java Thread类一起使用(参见下文)但除了堆栈跟踪之外,可能还有更多特定于上下文的信息,这些信息在调试的线程名称中很有用(对可以处理的许多队列或套接字之一的引用,例如在这种情况下您可能更喜欢特别针对该情况扩展Thread,以便您可以让编译器强制您(或其他使用您的库)传递某些信息(例如,有问题的队列/套接字)以便在名称中使用)。


这是一个通用线程的示例,其中调用堆栈跟踪作为其名称:


public class DebuggableThread extends Thread {
    private static String getStackTrace(String name) {
        Throwable t= new Throwable("DebuggableThread-"+name);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(os);
        t.printStackTrace(ps);
        return os.toString();
    }

    public DebuggableThread(String name) {
        super(getStackTrace(name));
    }

    public static void main(String[] args) throws Exception {
        System.out.println(new Thread());
        System.out.println(new DebuggableThread("MainTest"));
    }
}


这里是比较两个名字的输出样本:


Thread[Thread-1,5,main]
Thread[java.lang.Throwable: DebuggableThread-MainTest
    at DebuggableThread.getStackTrace(DebuggableThread.java:6)
    at DebuggableThread.<init>(DebuggableThread.java:14)
    at DebuggableThread.main(DebuggableThread.java:19)
,5,main]

其它参考15


由于这是一个非常受欢迎的话题,而且好的答案遍布各处并深入处理,我觉得将其他人的好答案汇编成更简洁的形式是合理的,因此新人有一个简单的概述:



  1. 您通常会扩展一个类来添加或修改功能。所以,如果您不想 覆盖任何线程行为,那么请使用Runnable。

  2. 同样,如果您不需要 继承线程方法,则可以使用Runnable完成开销

  3. 单继承:如果扩展Thread,则无法从任何其他类扩展,因此如果您需要这样做,则必须使用Runnable。

  4. 将域逻辑与技术手段分开是一个很好的设计,从这个意义上来说,最好让Runnable任务隔离 任务 runner 即可。

  5. 您可以执行多次运行对象,但是,一个Thread对象只能启动一次。 (也许是原因,为什么Executors接受Runnables,但不接受Threads。)

  6. 如果您将任务开发为Runnable,那么现在和将来都具有如何使用它的所有灵活性。您可以通过Executors同时运行它,也可以通过Thread运行它。而你仍然可以在同一个线程中非同时使用/调用它,就像任何其他普通类型/对象一样。

  7. 这使单元测试 分离任务逻辑和并发方面更容易。

  8. 如果您对此问题感兴趣,您可能也对Callable和Runnable之间的区别感兴趣。


其它参考16


这在Oracle的定义和开始一个主题教程中讨论过:[118]



  你应该使用哪些成语?第一个使用a的成语
  Runnable对象,更通用,因为Runnable对象可以
  子类除Thread以外的类。第二个习语更容易使用
  在简单的应用程序中,但受到您的任务的限制
  class必须是Thread的后代。本课重点介绍第一课
  方法,它将Runnable任务与Thread对象分开
  执行任务。这种方法不仅更灵活,而且
  它适用于所涵盖的高级线程管理API
  后来。



换句话说,实现Runnable将适用于类扩展Thread以外的类的场景。 Java不支持多重继承。此外,使用某些高级线程管理API时,无法扩展Thread。扩展Thread的唯一方案是在一个小型应用程序中,该应用程序将来不会受到更新。实现Runnable几乎总是更好,因为随着项目的增长它更灵活。设计更改不会产生重大影响,因为您可以在java中实现许多接口,但只扩展一个类。

其它参考17


如果我没有错,它或多或少类似于


接口和抽象类有什么区别?


extends建立了是一个的关系&界面提供具有功能。


首选 实现Runnable :



  1. 如果您不必扩展Thread类并修改Thread API默认实现

  2. 如果您正在执行火灾并忘记命令

  3. 如果您已经在扩展另一个班级



首选 扩展线程 :



  1. 如果必须覆盖oracle文档页面
  2. 中列出的任何这些Thread方法


通常,您不需要覆盖线程行为。因此,大多数时候首选实现Runnable [120]


另外,使用高级ExecutorServiceThreadPoolExecutorService API可提供更多灵活性和控制。


看看这个SE问题:


ExecutorService vs Casual Thread Spawner

其它参考18


扩展线程和实现Runnable之间的区别是:


[122]

其它参考19


将Thread类与Runnable实现分离也可以避免线程和run()方法之间潜在的同步问题。单独的Runnable通常在引用和执行可运行代码的方式方面提供更大的灵活性。

其它参考20


你想要实施的一个原因接口而不是扩展基类是因为您已经扩展了其他类。您只能扩展一个类,但可以实现任意数量的接口。


如果扩展Thread,你基本上会阻止你的逻辑被this之外的任何其他线程执行。如果你只想要一些线程来执行你的逻辑,那么最好只实现Runnable 。

其它参考21


如果使用runnable,则可以节省空间以扩展到任何其他类。

其它参考22


我们可以重新访问我们希望班级表现为Thread的基本原因吗?
没有任何理由,我们只是想执行一个任务,很可能是在异步模式下,这正是意味着任务的执行必须从我们的主线程和主线程分支,如果提前完成,可能会或可能不会等待对于分支路径(任务)。


如果这是整个目的,那么我在哪里可以看到需要专门的线程。这可以通过从系统的线程池中获取RAW线程并将其分配给我们的任务(可能是我们类的一个实例)来实现,就是这样。


因此,让我们遵守OOP概念并编写一个我们需要的类。有很多方法可以做到,以正确的方式做事很重要。


我们需要一个任务,所以编写一个可以在Thread上运行的任务定义。所以使用Runnable。


永远记住implements专门用于表示行为,extends用于赋予特征/属性。


我们不想要线程的属性,而是希望我们的类可以作为可以运行的任务来运行。

其它参考23


是,
如果调用ThreadA调用,那么在调用ThreadA类之后不需要调用start方法和run方法调用。
但是如果使用ThreadB调用则需要为调用run方法启动线程。
如果您有任何帮助,请回复我。

其它参考24


我发现使用Runnable最有用的原因是所有的原因,但有时我喜欢扩展Thread,所以我可以创建自己的线程停止方法并直接在我创建的线程上调用它。

其它参考25


Java不支持多重继承,因此如果扩展Thread类,则不会扩展其他类。


例如:如果你创建一个applet然后它必须扩展Applet类,所以这里创建线程的唯一方法是通过实现Runnable接口

其它参考26


这是固体的S:单一责任。[123] [124]


线程包含运行上下文(如执行上下文:堆栈帧,线程ID等)的异步执行码。理想情况下,代码应该是相同的实现,无论是同步还是异步。


如果在一个实现中将它们捆绑在一起,则会为结果对象提供两个无关的更改原因:



  1. 应用程序中的线程处理(即查询和修改执行上下文)

  2. 由一段代码(可运行部分)实现的算法



如果您使用的语言支持部分类或多重继承,那么您可以在其自己的超类中隔离每个原因,但它归结为与组合这两个对象相同,因为它们的功能集不重叠。这是为了理论。


实际上,一般来说,程序不需要承担比必要更复杂的程序。如果您有一个线程处理特定任务,而没有更改该任务,则可能没有必要将任务分开,并且您的代码仍然更简单。


在 Java 的上下文中,由于工具已经存在,因此可能更容易直接使用独立Runnable类,并将其实例传递给[[Thread(或Executor)实例。一旦使用到该模式,它就不会比简单的可运行线程情况更难使用(甚至读取)。

其它参考27


Thread和runnable之间的区别
如果我们使用Thread类创建Thread,那么线程数等于我们创建的对象数。
如果我们通过实现runnable接口创建线程,那么我们可以使用单个对象来创建多个线程。所以单个对象由多个Thread共享。因此它将占用更少的内存


因此,如果我们的数据不敏感,则取决于要求。所以它可以在多个Thread之间共享,我们可以使用Runnable接口。

其它参考28


在这里加两分钱 -
始终尽可能使用implements Runnable。以下是您不应该使用的两个警告
extends Thread S




  1. 理想情况下,你永远不应该扩展Thread类; Thread类应该final
    至少它的方法如thread.getId()
    有关扩展Thread的错误,请参阅此讨论。 [125]

  2. 那些喜欢解决谜题的人可以看到扩展Thread的另一个副作用。以下代码
    当没有人通知他们时,将打印无法访问的代码。



请参阅http://pastebin.com/BjKNNs2G。 [126]


public class WaitPuzzle {

    public static void main(String[] args) throws InterruptedException {
        DoNothing doNothing = new DoNothing();
        new WaitForever(doNothing).start();
        new WaitForever(doNothing).start();
        new WaitForever(doNothing).start();
        Thread.sleep(100);
        doNothing.start();
        while(true) {
            Thread.sleep(10);
        }
    }


    static class WaitForever extends  Thread {

        private DoNothing doNothing;

        public WaitForever(DoNothing doNothing) {
            this.doNothing =  doNothing;
        }

        @Override
        public void run() {
            synchronized (doNothing) {
                try {
                    doNothing.wait(); // will wait forever here as nobody notifies here
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Unreachable Code");
            }
        }
    }

    static class DoNothing extends Thread {

        @Override
        public void run() {
            System.out.println("Do Nothing ");
        }
    } 
}

其它参考29


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


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


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


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