2277

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

使用工具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

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

4

43 回答 43

1777

是的:RunnableIMO 是首选方法。您并没有真正专门化线程的行为。你只是给它一些运行的东西。这意味着组合哲学上“更纯粹”的方式。

实际上,这意味着您也可以从另一个类实现Runnable扩展......您还可以Runnable通过 Java 8 的 lambda 表达式来实现。

于 2009-02-12T14:32:54.340 回答
604

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

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

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

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

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

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

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

new FutureTask<Object>(threadA, null)

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

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

ExecutorService es = Executors.newCachedThreadPool();
es.execute(new ThreadA());
于 2009-02-12T14:37:05.390 回答
281

故事的道德启示:

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

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

少继承,多接口。

于 2010-03-11T15:50:54.523 回答
266

那么这么多好的答案,我想添加更多关于这个。这将有助于理解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 the 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,您需要创建 Thread 的实例并将其自身作为目标传入。

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

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

我希望这个能帮上忙!

于 2013-03-05T14:26:04.950 回答
86

令我惊讶的一件事还没有被提及,那就是实现Runnable使您的课程更加灵活。

如果您扩展线程,那么您正在执行的操作将始终在线程中。但是,如果您实施Runnable,则不必如此。你可以在一个线程中运行它,或者将它传递给某种执行器服务,或者只是将它作为一个单线程应用程序中的任务传递(可能稍后运行,但在同一个线程中)。如果您只使用这些选项,则Runnable比将自己绑定到Thread.

于 2009-02-12T14:51:35.123 回答
83

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

最常见的区别是

在此处输入图像描述

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

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

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

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

  • Runnable 接口表示一个可以通过普通线程或执行器或任何其他方式执行的任务。因此,将 Task 逻辑分离为 Runnable 而不是 Thread 是一个很好的设计决策。

  • 将任务分离为 Runnable 意味着我们可以重用任务,并且可以自由地从不同的方式执行它。因为一旦完成,您就无法重新启动线程。再次 Runnable 与 Thread 的任务,Runnable 是赢家。

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

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

javarevisited.blogspot.com提供

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

然而,显着的区别是。

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

于 2013-05-11T08:59:42.730 回答
80

Runnable实际上,相互比较和Thread相互 比较是不明智的。

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

我想说,多线程只有一种方法,分两步。让我提出我的观点。

可运行:
实现interface Runnable时意味着您正在创建run able位于不同线程中的东西。现在创建可以在线程内运行的东西(可在线程内运行),并不意味着创建线程。
所以这个类MyRunnable只不过是一个带有void run方法的普通类。它的对象将是一些普通的对象,只有一个run在调用时会正常执行的方法。(除非我们在线程中传递对象)。

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

为什么不进行比较?
因为我们需要它们来进行多线程。

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

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

所以从技术上和理论上来说,它们都是启动线程所必需的,一个会运行,一个会使其运行(就像Wheel and Engine汽车一样)。

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

但是有可能创建和运行一个线程只使用class Thread因为 ClassThread实现Runnable了所以我们都知道Thread也是一个Runnable里面。

最后ThreadRunnable在多线程方面相互补充,而不是竞争对手或替代品。

于 2015-05-12T13:50:15.080 回答
46

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

于 2009-02-12T14:41:22.293 回答
35

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

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

于 2009-02-12T14:32:01.487 回答
21

我想说还有第三种方式:

public class Something {

    public void justAnotherMethod() { ... }

}

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

也许这有点受我最近大量使用 Javascript 和 Actionscript 3 的影响,但是这样你的类就不需要实现像Runnable.

于 2010-10-25T21:41:00.943 回答
19

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

Runnable是一个函数式接口,这意味着它的实例可以使用 lambda 表达式或方法引用来创建。

您的示例可以替换为:

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

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

executor.execute(runner::run)

这些不仅比您的示例短得多,而且还具有使用Runnableover的其他答案中所述的许多优点Thread,例如单一职责和使用组合,因为您没有专门研究线程的行为。Runnable如果您只需要像您在示例中所做的那样,这种方式还可以避免创建额外的类。

于 2014-07-29T11:24:56.603 回答
18

实例化接口可以更清晰地分离代码和线程的实现,因此在这种情况下我更愿意实现 Runnable。

于 2009-02-12T14:33:09.207 回答
16

扩展线程和实现 Runnable 的区别在于:

在此处输入图像描述

于 2017-10-01T15:05:06.337 回答
13

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

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

new Thread(myRunnable,"WhateverNameiFeelLike");

但是,如果您扩展 Thread,那么您可以在类本身中管理它(就像在您的示例中您将线程命名为“ThreadB”)。在这种情况下,您:

A) 可能会给它一个更有用的名称以用于调试目的

B) 强制将该名称用于该类的所有实例(除非您忽略它是一个线程的事实并对其执行上述操作,就好像它是一个 Runnable 但无论如何我们在这里讨论的是约定所以可以忽略我感觉到的这种可能性)。

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

这似乎是一件小事,但是您有一个非常复杂的应用程序,其中有很多线程,并且突然之间事情“停止了”(可能是由于死锁的原因,也可能是因为网络协议中的缺陷会更少显而易见 - 或其他无穷无尽的原因)然后从 Java 获取堆栈转储,其中所有线程都称为 'Thread-1'、'Thread-2'、'Thread-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]
于 2012-06-19T23:12:49.857 回答
12

可运行,因为:

  • 为 Runnable 实现留下更大的灵活性来扩展另一个类
  • 将代码与执行分开
  • 允许您从线程池、事件线程或将来以任何其他方式运行您的可运行文件。

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

于 2010-05-07T04:28:57.637 回答
12

由于这是一个非常受欢迎的话题,而且好的答案遍布各处,并且处理得非常深入,我觉得将其他人的好答案编译成更简洁的形式是合理的,所以新手可以先简单地了解一下:

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

  2. 同样,如果您不需要继承线程方法,则可以通过使用 Runnable 来避免这种开销。

  3. 单继承:如果你扩展 Thread,你不能从任何其他类扩展,所以如果这是你需要做的,你必须使用 Runnable。

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

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

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

  7. 这也使得在单元测试中分离任务逻辑和并发方面变得更加容易。

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

于 2014-02-13T09:07:11.560 回答
9

这在 Oracle 的定义和启动线程教程中进行了讨论:

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

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

于 2014-01-21T16:44:09.417 回答
9

最简单的解释是通过实现Runnable我们可以将相同的对象分配给多个线程并且每个线程Thread共享相同的对象状态和行为。

例如,假设有两个线程,thread1将整数放入数组中,thread2在数组填满时从数组中取出整数。请注意,为了让thread2工作,它需要知道数组的状态,无论thread1是否已将其填满。

实现Runnable使您可以灵活地共享对象,同时extends Thread使您可以为每个线程创建新对象,因此线程1完成的任何更新都会丢失给线程2。

于 2015-08-14T08:07:40.853 回答
8

如果我没记错的话,它或多或少类似于

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

extends 建立“ Is A ”关系&接口提供“ Has a ”能力。

首选实现 Runnable

  1. 如果您不必扩展 Thread 类并修改 Thread API 默认实现
  2. 如果您正在执行火灾并忘记命令
  3. 如果您已经在扩展另一个类

首选“扩展线程”:

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

通常,您不需要覆盖 Thread 行为。因此,在大多数情况下,实现 Runnable是首选。

另一方面,使用高级ExecutorServiceThreadPoolExecutorServiceAPI 提供了更多的灵活性和控制力。

看看这个 SE 问题:

ExecutorService vs Casual Thread Spawner

于 2016-02-13T09:32:33.490 回答
6

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

于 2010-03-08T07:03:06.130 回答
6

Runnable是一个接口,而Thread是一个实现该接口的类。从设计的角度来看,任务的定义方式和执行方式之间应该有一个清晰的分离。前者是Runnalbe实现的责任,后者是Thread类的工作。在大多数情况下,实施Runnable是正确的做法。

于 2012-08-18T17:47:11.317 回答
6

这就是 SOLID 的S 单一责任。

线程体现了一段代码的异步执行运行上下文(如在执行上下文中:堆栈帧、线程 ID 等) 。理想情况下,这段代码应该是相同的实现,无论是同步的还是异步的。

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

  1. 应用程序中的线程处理(即查询和修改执行上下文)
  2. 由一段代码实现的算法(可运行部分)

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

在实践中,一般来说,一个程序不需要携带比必要的更复杂的东西。如果您有一个线程处理特定任务,而无需更改该任务,则将任务分开类可能没有意义,并且您的代码仍然更简单。

Java的上下文中,由于该工具已经存在,因此直接从独立Runnable类开始,并将它们的实例传递给Thread(或Executor)实例可能更容易。一旦习惯了这种模式,它并不比简单的可运行线程案例更难使用(甚至阅读)。

于 2013-03-18T10:14:31.730 回答
6

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

如果你扩展线程,你基本上是在阻止你的逻辑被除“this”之外的任何其他线程执行。如果您只想让某个线程执行您的逻辑,最好只实现 Runnable。

于 2013-07-25T17:29:00.160 回答
6

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

于 2013-09-26T05:58:02.810 回答
6

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

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

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

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

永远记住implements是专门用于传递行为并extends用于传递特征/属性。

我们不想要线程的属性,而是希望我们的类表现得像一个可以运行的任务。

于 2013-11-22T03:11:51.683 回答
5

是的,如果你调用 ThreadA 调用,那么不需要调用 start 方法,run 方法只是在调用 ThreadA 类之后调用。但是如果使用 ThreadB 调用则需要启动线程来调用 run 方法。如果您有更多帮助,请回复我。

于 2012-01-28T08:31:16.623 回答
5

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

于 2012-06-09T19:09:15.987 回答
5

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

例如:如果您创建一个小程序,那么它必须扩展 Applet 类,所以这里创建线程的唯一方法是实现 Runnable 接口

于 2012-06-09T19:28:31.597 回答
5

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

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

于 2014-03-05T06:13:58.437 回答
5

在这里添加我的两美分 - 总是尽可能使用implements Runnable。以下是关于为什么不应该使用 extends Threads的两个警告

  1. 理想情况下,您永远不应该扩展 Thread 类;Thread应该final上课。至少它的方法像thread.getId(). 有关与扩展s相关的错误,请参阅此讨论。Thread

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

请参阅 http://pastebin.com/BjKNNs2G

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 ");
        }
    } 
}
于 2015-01-03T00:11:56.637 回答
4

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

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

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

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

于 2015-02-19T05:37:40.550 回答
4

对于大多数工作线程来说,最好的方法是将线程完全封装在工作类中,这样就不会受到外部干扰并导致不需要和无效的线程/类状态。

我刚刚发布了一个示例,所以我也将与您分享:

/**
 * This worker can only run once
 * @author JayC667
 */
public class ProperThreading {

    private final Thread        mThread         = new Thread(() -> runWorkingLoop());   // if you want worker to be able to run multiple times, move initialisation into startThread()
    private volatile boolean    mThreadStarted  = false;
    private volatile boolean    mStopRequested  = false;

    private final long          mLoopSleepTime;

    public ProperThreading(final long pLoopSleepTime /* pass more arguments here, store in members */ ) {
        mLoopSleepTime = pLoopSleepTime;
    }

    public synchronized void startThread() {
        if (mThreadStarted) throw new IllegalStateException("Worker Thread may only be started once and is already running!");
        mThreadStarted = true;
        mThread.start();
    }

    private void runWorkingLoop() {
        while (!mStopRequested /* && other checks */ ) {
            try {
                // do the magic work here
                Thread.sleep(mLoopSleepTime);

            } catch (final InterruptedException e) {
                break;
            } catch (final Exception e) {
                // do at least some basic handling here, you should NEVER ignore exception unless you know exactly what you're doing, and then it should be commented!
            }
        }
    }

    public synchronized void stopThread() {
        if (!mThreadStarted) throw new IllegalStateException("Worker Thread is not even running yet!");
        mStopRequested = true;
        mThread.interrupt();
    }

}
于 2016-04-24T11:41:51.483 回答
3

线程持有不打算被访问的行为;

  • 它的同步锁用于连接等。
  • 它具有您可以偶然访问的方法。

但是,如果您将 Thread 子类化,则必须考虑实现更多 Thread。

public class ThreadMain {
    public int getId() {
        return 12345678;
    }

    public String getName() {
        return "Hello World";
    }

    public String getState() {
        return "testing";
    }

    public void example() {
        new Thread() {
            @Override
            public void run() {
                System.out.println("id: "+getId()+", name: "+getName()+", state: "+getState());
            }
        }.start();
    }

    public static void main(String[] args) {
        new ThreadMain().example();
    }
}

如果你运行它,你可能会期望

id: 12345678, name: Hello World, state: testing

但是,您没有调用您认为的方法,因为您使用的方法Thread不是ThreadMain,而是您看到类似

id: 11, name: Thread-0, state: RUNNABLE
于 2015-07-26T18:01:29.560 回答
3

1. 扩展线程接口,就像你让你的类只表现为一个线程。您的新课程将像一个增强线程。

jshell> public class Test extends Thread{
   ...> public Test(String name){
   ...> super(name);
   ...> }
   ...> public void run(){
   ...> System.out.println(Thread.currentThread().getName());
   ...> }
   ...> }
|  created class Test

jshell> Test t1=new Test("MyThread");
t1 ==> Thread[MyThread,5,main]

它创建一个线程,而不是Test对象。所以它会像一个线程一样工作。您不能Test在线程之间共享类的实例。

2.实现runnable接口。

jshell> public class Test1 implements Runnable{
   ...> public void run(){
   ...> System.out.println(Thread.currentThread().getName());
   ...> }
   ...> public String getName(){
   ...> return "testing";}
   ...> }
|  created class Test1

jshell> Test1 t1=new Test1();
t1 ==> Test1@396a51ab  --> this creates Test1 object.

该对象可以通过以下方式跨线程共享,

jshell> Thread t1=new Thread(t1,"Hai");
t ==> Thread[Hai,5,main]

jshell> Thread t=new Thread(t1,"Hai");
t ==> Thread[Hai,5,main]

我认为关于这个主题已经讨论了很多,认为这可能对基础知识有所帮助。

于 2019-05-03T14:13:47.450 回答
2

这可能不是答案,但无论如何;还有另一种创建线程的方法:

Thread t = new Thread() {
    public void run() {
        // Code here
    }
}
于 2012-07-06T15:12:39.930 回答
2

在极少数情况下您只运行一次,由于 DRY,您应该扩展 Thread。如果你多次调用它,你应该实现 Runnable 因为同一个线程不应该重新启动。

于 2016-11-10T07:05:51.520 回答
2

通过扩展线程类,派生类不能扩展任何其他基类,因为java只允许单继承。相反,通过实现可运行接口,该类仍然扩展了其他基类。

下面给出了实现 Runnable 和扩展 Thread 之间最显着的区别:

通过扩展 Thread,派生类本身就是一个线程对象,而实现 Runnable 接口则将同一个对象共享给多个线程。

于 2017-04-23T05:14:01.120 回答
1

Thread 类定义了几个方法,可以overriden通过扩展类。但是要创建一个线程,我们必须重写该run()方法。同样适用于Runnable

然而Runnable,这是创建线程的首选方法。主要原因是:

  1. 由于 Runnable 是一个接口,因此您可以扩展其他类。但是,如果您扩展 Thread,那么该选项就消失了。

  2. 如果您不修改或增强大量Thread功能并且扩展Thread类不是首选方式。

于 2017-10-22T07:12:17.530 回答
1

如果您想分离您的关注点,例如,如果您想创建任务并且谁是处理您的任务的工作人员并不重要,那么使用 Runnable 并将您的工作定义为任务。就像使用 executor service 时一样,您以 Runnable/callable 的形式定义任务,并将它们交给 executor service,然后由 worker 负责。

如果您想自己创建一个工作人员并将该工作人员分配给您的任务,您需要对工作线程进行更多控制,然后使用 Thread 类。

于 2022-02-12T14:58:11.043 回答
0

简单的说法是:如果您实现接口,这意味着您正在实现它的所有方法,并且如果您扩展类,您正在继承您选择的方法......在这种情况下,只有一个名为 Run() 的方法,所以更好地实现 Runnable 接口..

于 2014-06-18T07:22:39.580 回答
0

我会说实际任务与线程分离。在 Runnable 的情况下,我们可以将任务传递给 Thread、Executor 框架等,而扩展 Thread 任务与线程对象本身相耦合。扩展 Thread 时无法进行任务隔离。这就像我们将任务烧录到 Thread 对象,就像 IC 芯片一样(更具体地说,不会得到任何任务句柄)。

于 2018-09-02T10:02:50.967 回答
-1

您可以共同使用它们

例子 :

public class A implements Runnable{

    @Override
    public void run() {


        while(true){
             System.out.println("Class A is running");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }}

    }

}



public class Test {

    public static void main(String[] args) {

        Thread myThread =new Thread(new A());// 1
        myThread.start();
        System.out.println(" executed after thread A");//will never be reached

    }

} 
于 2018-04-25T20:21:53.383 回答
-1

Thread 和 Runnable 的主要区别是: - Thread 就像:Worker(执行 Runnable) - Runnable 就像:Job(由 Thread 执行)

于 2018-12-05T03:58:43.357 回答