ChatGPT解决这个技术问题 Extra ChatGPT

Java中的“实现可运行”与“扩展线程”

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

使用实现Runnable

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

或者,使用 扩展 Thread

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

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

感谢这个问题,答案消除了我的很多误解。在 SO 存在之前,我研究了执行 Java 线程的正确方法,并且那里有很多错误信息/过时的信息。
您可能想要扩展 Thread 的原因之一(但我不推荐它),您可以抢先处理 interrupt()。同样,这是一个想法,它可能在正确的情况下有用,但我不推荐它。
另请参阅答案,解释得很好:stackoverflow.com/q/5562720/285594
@bestsss,我试图弄清楚您对处理中断()的含义。您是否要覆盖该方法?
是的。根据代码,线程 A 类可以扩展任何类,而线程 B 类不能扩展任何其他类

J
Jon Skeet

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

实用术语中,这意味着您可以实现 Runnable 并从另一个类扩展......您还可以通过 Java 8 的 lambda 表达式实现 Runnable


没错,说得好。我们试图通过扩展它来覆盖 Thread 中的什么行为?我认为大多数人并没有试图覆盖任何行为,而是试图使用 Thread 的行为。
作为旁注,如果您实例化一个 Thread 并且不调用它的 start() 方法,那么您将在 Java 中创建内存泄漏 < 5(Runnables 不会发生这种情况):stackoverflow.com/questions/107823/…
Runnable 的一个小优点是,如果在某些情况下您不关心或不想使用线程,而只想执行代码,则可以选择简单地调用 run()。例如(非常随意)if (numberCores > 4) myExecutor.excute(myRunnable); else myRunnable.run()
@user949300 您也可以使用 extends Thread 来做到这一点,如果您不想线程化,为什么还要实现 Runnable...
套用 Sierra 和 Bates 的话说,实施 Runnable 的一个主要好处是您在架构上将“工作”与“跑步者”分开。
c
cngzz1

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

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

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

跟进:有一个 FutureTask constructor 允许您使用 Runnables(如果这是您最熟悉的),并且仍然可以从现代并发工具中受益。 To quote the 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());

这比接受的答案恕我直言要好。一件事:您拥有的代码片段并没有关闭执行程序,我看到数百万个问题,人们在哪里弄错了,每次他们想要生成一个任务时都会创建一个新的执行程序。 es 作为静态(或注入)字段会更好,因此它只会创建一次。
@artbristol,谢谢!我不反对新的 Executor(我们按照您在代码中的建议进行操作)。在编写原始答案时,我试图编写类似于原始片段的最少代码。我们必须希望这些答案的许多读者将它们用作起点。我不是想为 javadoc 写一个替代品。我正在为它有效地编写营销材料:如果你喜欢这种方法,你应该看到我们提供的所有其他很棒的东西......!
我知道我对此发表评论有点晚了,但是直接处理 FutureTask 通常不是您想要做的。当您向 submit 发送 Runnable/Callable 时,ExecutorService 将为您创建适当的 Future。当您 schedule 成为 Runnable/Callable 时,对于 ScheduledExecutorServiceScheduledFuture 也是如此。
@Powerlord,我的意图是制作与 OP 尽可能匹配的代码片段。我同意新的 FutureTask 不是最优的,但为了解释的目的很清楚。
在第一段中,您提到“如果您使用 Runnable 并稍后决定这实际上不需要它自己的线程,您可以调用 threadA.run()”。你的意思是调用runnable.run()?你能展示一些你所指的代码吗?
s
spongebob

故事的道德启示:

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

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

少继承,多接口。


如果您开始制作并发运行的对象,这应该始终是问题!你甚至需要线程对象功能吗?
从 Thread 继承时,几乎总是希望覆盖 run() 方法的行为。
您不能通过覆盖 run() 方法来覆盖 java.lang.Thread 的行为。在这种情况下,您需要覆盖我猜的 start() 方法。通常,您只需通过将执行块注入 run() 方法来重用 java.lang.Thread 的行为。
继承不仅仅是为了覆盖某些行为,也是为了使用常见的行为。反之亦然,覆盖越多,等级越差。
d
dantechguy

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

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

当您扩展 Thread 类时,之后您将无法扩展您需要的任何其他类。 (如您所知,Java 不允许继承多个类)。当您实现 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 接口的同时扩展另一个类来做一个线程。

我希望这个能帮上忙!


您的代码显然是错误的。我的意思是,它做了它所做的,但不是你想要展示的。
澄清一下:对于 runnable 案例,您使用相同的 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(); 是否更清楚?
我还不明白你的意图,但我的意思是,如果你创建多个 ExtendsThread 实例——它们都将返回 1(如你所示)。您可以通过在此处执行相同的操作(即创建多个 ImplementsRunnable 实例)来为 Runnable 获得相同的结果。
@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 是否错误?如果不是,那么证明这一点的案例是什么?
@EvilWashingMachine:很久没有活动了.. 刚刚看到这个。我已将对象的哈希码添加到打印语句... + " hashcode: " + this.hashCode()
A
Andrew Tobilko

令我惊讶的一件事是尚未提及,实现 Runnable 使您的类更加灵活。

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


好吧,您实际上也可以用 Thread 对象做同样的事情,因为 Thread implements Runnable... ;-) 但是用 Runnable 做这些事情比用 Thread 做这些事情“感觉更好”!
没错,但 Thread 添加了很多您不需要的额外内容,而且在很多情况下也不需要。你总是最好实现与你实际正在做的事情相匹配的接口。
S
Stuphan

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

最常见的区别是

https://i.stack.imgur.com/vLRdp.gif

当您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 时,它将同一个对象共享给多个线程。


S
Saif

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

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

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

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

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

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

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

可以在线程内运行的东西(可运行)。

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

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

这就是为什么您不能使用 MyRunnable 启动线程的原因,您需要将其传递给 Thread 的实例。

但是可以只使用 class Thread 创建和运行线程,因为类 Thread 实现了 Runnable,所以我们都知道 Thread 也是一个 Runnable 内部。

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


确切地!这应该是公认的答案。顺便说一句,我认为问题已被编辑,ThreadA 不再有意义
接受的答案更多代表感谢您的回复@idelvall
最佳答案!谢谢!
C
Community

您应该实现 Runnable,但如果您在 Java 5 或更高版本上运行,则不应使用 new Thread 启动它,而应使用 ExecutorService。详情见:How to implement simple threading in Java


如果您只想启动一个线程,我认为 ExecutorService 不会那么有用。
从我所了解到的情况来看,通常不应再自行启动线程,因为将其留给执行程序服务会使一切变得更加可控(例如,等待线程挂起)。另外,我在问题中看不到任何暗示它与单个线程有关的内容。
如果我们事先知道它将是一个单线程,那么使用任何多线程有什么意义。所以让我们假设我们有多个线程,这个答案很有价值。
@zEro 我很确定只有一个事件调度线程是有原因的。我怀疑这是唯一一个最好有一个单独的线程但可能不是最好有多个线程的情况。
P
Powerlord

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

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


在 runnable 中我们不能进行网络调用,是吗?因为我有 android.os.NetworkOnMainThreadException。但是通过使用线程,我可以进行网络调用。如果我错了,请纠正我。
@NabeelThobani Normal Java 不在乎,但听起来像 Android 一样。不过,我对 Android 还不够熟悉。
@NabeelThobani 当然可以。可能您没有使用 Runnable 创建线程。
B
Bart van Heukelom

我想说还有第三种方式:

public class Something {

    public void justAnotherMethod() { ... }

}

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

也许这受我最近大量使用 Javascript 和 Actionscript 3 的影响,但这样你的类就不需要实现像 Runnable 这样的非常模糊的接口。


这真的不是第三种方式。您仍在实现 Runnable,只是匿名进行。
@Don Roby:这是不同的。这通常很方便,您可以使用包含类/方法的字段和最终局部变量。
@BartvanHeukelom 这很方便,但没有什么不同。您可以对任何类型的嵌套类执行此操作,即内部类、本地类和 lambda 表达式。
A
Alex - GlassEditor.com

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

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

您的示例可以替换为:

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

或者,如果您想使用 ExecutorService 和方法引用:

executor.execute(runner::run)

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


这个答案需要解释。经过一番困惑,我得出结论 () -> {} 应该代表某人需要的自定义逻辑?所以说() -> { /* Code here */ }会更好吗?
s
starblue

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


R
Raman Gupta

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

https://i.stack.imgur.com/n1DJO.jpg


A
AntonyM

这里的每个人似乎都认为实现 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]

cHao 你的意思是什么?您不能在线程执行期间使用上面的代码来获取线程创建的堆栈跟踪(相反,您会得到一个简单的名称或最多是线程启动的堆栈跟踪),但是通过子类化线程,您可以做到这一点并强制它,甚至需要进一步的上下文特定信息,从而让您更具体地了解究竟哪个线程可能有问题。
我的观点是“如果你实现 Runnable,那么实现 Runnable 的类就无法控制线程名......”显然是错误的。实现 Runnable 的类确实可以控制线程名称,因为运行代码的线程根据定义是当前线程(并且通过安全检查的任何代码都可以控制线程名称)。考虑到您将一半的帖子用于“天哪,线程名称怎么样!”,这似乎是一件大事。
线程名称?也没有什么能阻止你扩展线程类。
n
n13

可运行,因为:

为 Runnable 实现留下更大的灵活性来扩展另一个类

将代码与执行分开

允许您从线程池、事件线程或将来以任何其他方式运行您的可运行文件。

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


C
Community

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

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


@Pino是的,线程本身也是一个可运行的。但是,如果您将其扩展为仅将其用作 Runnable,那有什么意义呢?为什么不直接使用没有所有包袱的普通 Runnable。所以,我认为,如果你扩展 Thread,你也可以使用它的 start 方法来执行它,这个方法只能使用一次。这就是 Nidhish-Krishnan 想要在他的回答中提出的观点。请注意,我的只是此处其他答案的汇编或简要摘要。
S
Sionnach733

这在 Oracle 的 Defining and Starting a Thread 教程中进行了讨论:

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

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


S
Shababb Karim

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

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

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


C
Community

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

What is the difference between an interface and abstract class?

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

首选实现 Runnable :

如果您不必扩展 Thread 类并修改 Thread API 默认实现 如果您正在执行 fire and forget 命令 如果您已经在扩展另一个类

首选“扩展线程”:

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

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

另一方面,使用高级 ExecutorServiceThreadPoolExecutorService API 可提供更大的灵活性和控制力。

看看这个 SE 问题:

ExecutorService vs Casual Thread Spawner


G
Govula Srinivas

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


d
developer110

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


d
didierc

这就是 SOLIDS:单一职责。

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

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

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

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

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

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


N
Nikhil A A

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

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


是的,通过实现 Runnable 接口可以自由地通过扩展任何类来实现自己的逻辑,这就是为什么 Runnable 比 Thread 类更受欢迎的原因。
u
user2771655

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


d
dharam

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

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

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

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

永远记住 implements 专门用于传递行为,而 extends 用于传递特性/属性。

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


M
Manoj Kumar

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


T
Tarvaris Jackson

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


H
Himanshu Mohta

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

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


R
Rohit Chugh

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

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


k
kinbiko

在这里加上我的两分钱 - 总是尽可能使用 implements Runnable 。以下是关于为什么不应该使用 extends Threads 的两个注意事项

理想情况下,您永远不应该扩展 Thread 类; Thread 类应该是最终的。至少它的方法像 thread.getId()。有关与扩展线程相关的错误,请参阅此讨论。喜欢解谜的人可以看到扩展 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 ");
        }
    } 
}