Java中的“实现Runnable”与“扩展线程”

问题描述 投票:1926回答:42

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

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

java multithreading runnable implements java-threads
42个回答
1560
投票

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

实际上,它意味着您可以实现Runnable并从另一个类扩展。


19
投票

我想说还有第三种方式:

public class Something {

    public void justAnotherMethod() { ... }

}

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

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


16
投票

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

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

您的示例可以替换为:

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

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

executor.execute(runner::run)

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


15
投票

实例化一个接口可以更清晰地分离代码和线程的实现,所以我更喜欢在这种情况下实现Runnable。


11
投票

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

10
投票

Runnable因为:

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

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


10
投票

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

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

8
投票

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

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

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


8
投票

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

enter image description here


6
投票

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

What is the difference between an interface and abstract class?

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

首选实现Runnable:

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

首选“扩展线程”:

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

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

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

看看这个SE问题:

ExecutorService vs Casual Thread Spawner


6
投票

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

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

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


523
投票

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

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

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

后续行动:有一个FutureTask constructor允许您使用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());

5
投票

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


5
投票

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

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


5
投票

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


5
投票

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

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

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

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

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

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


4
投票

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


4
投票

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


4
投票

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

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


4
投票

那是SSOLID:单一责任。

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

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

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

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

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

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


4
投票

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

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


4
投票

在这里加上我的两分钱 - 总是尽可能使用implements Runnable。以下是关于你不应该使用extends Threads的两个警告

  1. 理想情况下,您永远不应该扩展Thread类; Thread类应该是final。至少它的方法像thread.getId()。有关扩展thiss的错误,请参阅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 ");
        }
    } 
}

246
投票

故事的道德启示:

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

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

继承少,界面更多。


4
投票

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

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

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

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


207
投票

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

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

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

我希望这个能帮上忙!


75
投票

我还没有提到一件令人惊讶的事情是,实施Runnable会让你的课程变得更加灵活。

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


70
投票

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

最常见的区别是

当你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类时,你的每个线程都会创建一个唯一的对象并与之关联。当你implements Runnable时,它与多个线程共享同一个对象。


66
投票

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

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

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

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

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

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

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

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

因此从技术上和理论上来说,它们都是启动螺纹所必需的,一个将运行,一个将使其运行(如机动车的Wheel and Engine)。

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

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

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


42
投票

您应该实现Runnable,但是如果您在Java 5或更高版本上运行,则不应该使用new Thread启动它,而是使用ExecutorService。有关详细信息,请参阅:How to implement simple threading in Java


32
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.