首页 文章

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

提问于
浏览
1859

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

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

30 回答

  • 4

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

  • 6

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

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

  • 233

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

  • 4

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

  • 41

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

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

  • 10

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

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

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

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

  • 10

    加我两分钱在这里 - Always whenever possible use implements Runnable . Below are two caveats on why you should not use extends Threads

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

    • 那些喜欢解决谜题的人可以看到扩展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 ");
            }
        } 
    }
    
  • 10

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

  • 4

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

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

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

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

  • 61

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

    Runnablefunctional interface,这意味着可以使用lambda表达式或方法引用创建它的实例 .

    您的示例可以替换为:

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

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

    executor.execute(runner::run)
    

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

  • 4

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

  • 5

    tl;dr: implements Runnable is better. However, the caveat is important

    一般来说,我建议使用像 Runnable 而不是 Thread 这样的东西,因为它允许你保持你的工作只与你选择的并发性松散耦合 . 例如,如果您使用 Runnable 并稍后决定这不会't in fact require it'拥有 Thread ,则可以调用threadA.run() .

    Caveat: 在这里,我强烈反对使用原始线程 . 我更喜欢使用CallablesFutureTasks(来自javadoc:"A cancellable asynchronous computation") . 超时,正确取消和现代并发支持的线程池的集成对我来说比成堆的原始线程更有用 .

    Follow-up: 有一个FutureTask constructor允许你使用Runnables(如果这是你最熟悉的)并且仍然可以获得现代并发工具的好处 . 引用javadoc:

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

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

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

    new FutureTask<Object>(threadA, null)
    

    允许您更接近Runnables的另一个选项是ThreadPoolExecutor . 您可以使用execute方法传入Runnable来执行"the given task sometime in the future."

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

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

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

    What is the difference between an interface and abstract class?

    extends Build “ Is A " relation & interface provides " Has a ”能力 .

    首选 implements Runnable

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

    • 如果您正在执行fire and forget命令

    • 如果您已经在扩展另一个 class

    喜欢“ extends Thread ”:

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

    通常,您不需要重写Thread行为 . 所以 implements Runnable 在大多数时候都是首选 .

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

    看看这个SE问题:

    ExecutorService vs Casual Thread Spawner

  • 4

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

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

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

  • 1489

    故事的道德启示:

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

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

    继承少,界面更多 .

  • 5

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

    • 您通常扩展一个类来添加或修改功能 . 所以,如果你不想 overwrite 任何 Thread behavior ,那么使用Runnable .

    • 同样,如果你不需要 inherit 线程方法,你可以使用Runnable不用 overhead .

    • Single inheritance :如果扩展Thread,则无法从任何其他类扩展,因此如果您需要这样做,则必须使用Runnable .

    • 将域逻辑与技术手段区分开来是一个很好的设计,从这个意义上来说,最好有一个Runnable任务 isolating 你的 task from 你的 runner .

    • 你可以 execute 相同的Runnable object multiple times ,一个Thread对象,但只能启动一次 . (也许是原因,为什么Executors接受Runnables,但不接受Threads . )

    • 如果您将任务开发为Runnable,则 all flexibility how to use it now and in the future . 您可以通过Executors同时运行它,也可以通过Thread运行它 . 而你仍然可以在同一个线程中非同时使用/调用它,就像任何其他普通类型/对象一样 .

    • 这使得 separate 任务逻辑和并发更容易 aspects in 你的 unit tests .

    • 如果您对此问题感兴趣,您可能也对difference between Callable and Runnable感兴趣 .

  • 67

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

  • 8

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

    The most common difference is

    enter image description here

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

    当您 implements Runnable 时,您可以为您的 class 节省空间,以便将来或现在扩展任何其他课程 .

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

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

    • Runnable接口表示可以由普通线程或 Actuator 或任何其他方式执行的任务 . 所以将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接口 .

    However, the significant difference is.

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

  • 4

    这是SOLIDS:单一责任 .

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

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

    应用程序中的

    • 线程处理(即查询和修改执行上下文)

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

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

    实际上,一般来说,程序不需要承担比必要更复杂的程序 . 如果你有一个线程在工作特定的任务,没有改变任务,可能没有必要使任务分开类,你的代码仍然更简单 .

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

  • 5

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

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

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

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

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

    Why not wise to compare?
    因为我们需要它们用于多线程 .

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

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

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

    因此从技术上和理论上讲,它们都是启动螺纹所必需的,一个将 run ,一个将 make it run (如机动车的 Wheel and Engine ) .

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

    But 只能使用 class Thread 创建和运行一个线程,因为类 Thread 实现 Runnable 所以我们都知道 Thread 也是 Runnable 里面 .

    Finally Thread and Runnable are complement to each other for multithreading not competitor or replacement.

  • 16

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

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

    • 当您扩展Thread类时,之后您无法扩展您需要的任何其他类 . (如您所知,Java不允许继承多个类) .

    • 实现Runnable时,可以为类保存空间,以便将来或现在扩展任何其他类 .

    但是,实现Runnable和扩展Thread之间的一个 significant difference 就是这样
    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.

    The following example helps you to understand more clearly

    //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();
     }
    }
    

    Output of the above program.

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

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

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

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

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

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

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

    我希望这个能帮上忙!

  • 74

    是的:实现 Runnable 是首选方式做吧,IMO . 你're not really specialising the thread'的行为 . 你只是给它一些东西来运行 . 这意味着composition是哲学上的方式 .

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

  • 15

    我想说还有第三种方式:

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

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

  • 505

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

    如果你扩展Thread,你're basically preventing your logic to be executed by any other thread than '这个' . 如果您只想要一些线程来执行您的逻辑,那么最好只实现Runnable .

  • 4

    我提到的一件事是,实施 Runnable 会让你的课程变得更加灵活 .

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

  • 193

    Runnable因为:

    • 为Runnable实现提供了更大的灵活性来扩展另一个类

    • 将代码与执行分开

    • 允许您从线程池,事件线程或将来以任何其他方式运行runnable .

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

  • 4

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

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

  • 19

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

  • 7

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

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

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

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

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

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

相关问题