“implements Runnable” vs “extends Thread” in Java
从我在爪哇使用线程的时间来看,我发现了这两种方法来编写线程:
使用
1 2 3 4 5 6 | public class MyRunnable implements Runnable { public void run() { //Code } } //Started with a"new Thread(new MyRunnable()).start()" call |
或者,使用
1 2 3 4 5 6 7 8 9 | public class MyThread extends Thread { public MyThread() { super("MyThread"); } public void run() { //Code } } //Started with a"new MyThread().start()" call |
这两个代码块有什么显著的区别吗?
是的:IMO,实现
实际上,它意味着您可以实现
tl;dr:实现可运行性更好。但是,警告很重要
一般来说,我建议使用
警告:在这里,我强烈反对使用原始线程。我更喜欢使用可调用和未来任务(来自javadoc:"可取消的异步计算")。与成堆的原始线程相比,超时、适当的取消和现代并发支持的线程池的集成对我来说更有用。
后续:有一个
如果不需要特定的结果,请考虑使用表单的构造:
1 | Future<?> f = new FutureTask<Object>(runnable, null) |
因此,如果我们用你的
1 | new FutureTask<Object>(threadA, null) |
另一个允许您离runnables更近的选项是threadpoolexecutor。可以使用execute方法传入可运行文件以执行"将来某个时候给定的任务"。
如果您想尝试使用线程池,上面的代码片段将类似以下内容(使用executors.newCachedThreadPool()工厂方法):
1 2 | ExecutorService es = Executors.newCachedThreadPool(); es.execute(new ThreadA()); |
故事的寓意:
仅当要重写某些行为时才继承。
或者应该理解为:
继承更少,接口更多。
好吧,这么多好的答案,我想在上面再加一点。这将有助于理解
两种方法都有相同的作用,但也存在一些差异。最常见的区别是
然而,实现可运行线程和扩展线程之间的一个显著区别是
下面的示例将帮助您更清楚地理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | //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(); } } |
上述程序的输出。
1 2 3 4 5 6 | ImplementsRunnable : Counter : 1 ImplementsRunnable : Counter : 2 ImplementsRunnable : Counter : 3 ExtendsThread : Counter : 1 ExtendsThread : Counter : 1 ExtendsThread : Counter : 1 |
在可运行的接口方法中,只创建了一个类的实例,并由不同的线程共享。因此,计数器的值对于每个线程访问都是递增的。
然而,线程类方法,必须为每个线程访问创建单独的实例。因此,为每个类实例分配了不同的内存,并且每个实例都有单独的计数器,该值保持不变,这意味着不会发生增量,因为没有一个对象引用是相同的。
何时使用runnable?如果要从线程组访问相同的资源,请使用可运行接口。避免在这里使用线程类,因为创建多个对象会消耗更多的内存,并且会造成很大的性能开销。
实现可运行的类不是线程,而是类。要使可运行文件成为线程,需要创建线程的实例并将其自身作为目标传入。
在大多数情况下,如果您只打算重写
当需要扩展超类时,实现可运行接口比使用线程类更合适。因为我们可以在实现可运行接口时扩展另一个类来生成线程。
希望这能有所帮助!
有一件事我很惊讶,但还没有提到,实现
如果您扩展线程,那么您正在执行的操作将始终处于线程中。但是,如果您实现
如果要实现或扩展任何其他类,则最好使用
最常见的区别是
当你上1〔5〕课后,你就不能再扩展任何你需要的其他课了。(如您所知,Java不允许继承多个类)。
当您使用cx1〔6〕时,您可以为您的类节省空间,以便将来或现在扩展任何其他类。
Java不支持多个继承,这意味着只能在Java中扩展一个类,所以一旦扩展了线程类,就失去了机会,不能在Java中扩展或继承另一个类。
在面向对象编程中,扩展类通常意味着添加新功能,修改或改进行为。如果我们不在线程上做任何修改,那么使用可运行的接口。
可运行接口表示一个任务,可以通过普通线程、执行器或任何其他方式执行。因此,将任务逻辑分离为可运行的而非线程是一个很好的设计决策。
将任务分离为可运行的意味着我们可以重用该任务,并且可以从不同的方法执行它。因为线程一旦完成就不能重新启动。再次运行vs线程的任务,运行是胜利者。
Java设计器认识到这一点,这就是为什么执行器接受Runnabl作为任务,并且它们有执行这些任务的工作线程。
继承所有线程方法是额外的开销,仅仅是为了表示一个可以用runnable轻松完成的任务。
由javarevisited.blogspot.com提供
这些是Java中线程和可运行线程之间的一些显著差异。如果您知道线程和可运行线程之间的任何其他区别,请通过注释来共享。我个人将runnable over thread用于这个场景,并建议根据您的需求使用runnable或callable接口。
然而,显著的差异是。
当
实际上,将
这两者在多线程中有依赖关系,就像汽车的
我想说,只有一种方法可以实现两步多线程。让我说明我的观点。
Runnable:在实现
线程:
为什么不明智地比较?因为我们都需要它们来实现多线程。
对于多线程,我们需要两件事:
- 可以在线程内部运行的东西(可运行的)。
- 可以启动新线程(线程)的东西。
所以从技术上和理论上来说,这两个都是启动线程所必需的,一个线程将运行,另一个线程将使其运行(如电动汽车的
这就是为什么你不能用
但是只使用
最后,
您应该实现RunnEnabl,但是如果您在Java 5或更高版本上运行,则不应该用EDCOX1 OR 9来启动它,而是使用ExtutoService来代替它。有关详细信息,请参见:如何在Java中实现简单线程。
我不是专家,但我能想到一个实现RunnFabor而不是扩展线程的理由:Java只支持单继承,所以只能扩展一个类。
编辑:最初的说法是"实现一个接口需要更少的资源"。同样,但是您需要以任何方式创建一个新的线程实例,所以这是错误的。
我想说还有第三种方法:
1 2 3 4 5 6 7 8 9 10 11 |
也许这会受到我最近大量使用JavaScript和ActionScript3的影响,但是这样您的类就不需要实现一个相当模糊的接口,比如
随着Java 8的发布,现在有第三个选项。
您的示例可以替换为:
1 |
或者,如果要使用
1 | executor.execute(runner::run) |
这些不仅比您的例子短得多,而且还具有使用
实例化一个接口可以在代码和线程实现之间提供一个更清晰的分离,因此在本例中,我更希望实现可运行的。
可运行是因为:
- 为可运行的实现以扩展另一个班
- 将代码与执行
- 允许您运行可从线程池运行,事件线程,或以任何其他方式未来。
即使你现在不需要这些,将来也可以。因为覆盖线程没有好处,所以runnable是更好的解决方案。
这里的每个人似乎都认为实现runnable是一种可行的方法,我并不真正反对它们,但在我看来,还有一种情况需要扩展线程,实际上您已经在代码中演示了它。
如果实现runnable,那么实现runnable的类对线程名没有控制权,它是可以设置线程名的调用代码,如:
1 |
但是,如果扩展线程,那么就可以在类本身中管理它(就像在示例中,您将线程命名为"threadb")。在这种情况下,您:
a)可能会为调试提供一个更有用的名称
b)强制将该名称用于该类的所有实例(除非您忽略它是一个线程的事实,并将其作为可运行的线程进行上述操作,但我们在这里讨论的是约定,因此可以忽略我感觉到的可能性)。
例如,您甚至可以获取其创建的堆栈跟踪,并将其用作线程名。这可能看起来很奇怪,但取决于代码的结构,它对于调试非常有用。
这看起来像是一件小事,但在那里你有一个非常复杂的应用程序,很多线程都突然停止了(因为死锁的原因或者可能是因为网络协议中的缺陷而不太明显或者其他原因),然后从Java中获得一个堆栈转储,其中所有线程都是CA。alled'thread-1'、'thread-2'、'thread-3'并不总是非常有用的(这取决于线程的结构以及您能否有效地通过它们的堆栈跟踪来判断哪个线程是哪个线程——如果您使用的是多个线程组,所有线程都运行相同的代码,则不总是可能的)。
已经说过,当然,也可以通过创建线程类的扩展来完成上述操作,该线程类将其名称设置为其创建调用的堆栈跟踪,然后使用可运行的实现而不是标准的Java线程类来使用它(见下文),但是除了堆栈跟踪之外,还可能有更多的上下文规范。将在线程名称中有用的用于调试的FIC信息(它可以处理的多个队列或套接字中的一个的引用,例如,在这种情况下,您可能希望针对该情况专门扩展线程,以便编译器强制您(或其他使用库的人)传递某些信息(例如,队列/套接字n问题)用于名称)。
下面是一个以调用堆栈跟踪为名称的通用线程示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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")); } } |
下面是比较两个名称的输出示例:
1 2 3 4 5 6 |
由于这是一个很受欢迎的话题,好的答案被广泛传播和深入处理,我觉得将其他人的好答案汇编成一个更简洁的形式是有道理的,因此新来者可以很容易地提前概述:
通常扩展类以添加或修改功能。所以,如果您不想覆盖任何线程行为,那么使用runnable。
在同样的情况下,如果不需要继承线程方法,可以使用runnable在没有开销的情况下进行。
单一继承:如果您扩展线程,就不能从任何其他类进行扩展,所以如果您需要这样做,就必须使用runnable。
将域逻辑与技术手段分离是一种好的设计,从这个意义上说,最好有一个可运行的任务将任务与运行者隔离开来。
可以多次执行同一个可运行对象,但是线程对象只能启动一次。(可能是因为,执行器接受可运行文件,但不接受线程。)
如果您将任务开发为可运行的,那么您现在和将来都可以灵活地使用它。您可以让它通过执行器同时运行,也可以通过线程运行。您仍然可以在同一线程中非并发地使用/调用它,就像任何其他普通类型/对象一样。
这也使得在单元测试中分离任务逻辑和并发方面更容易。
如果您对这个问题感兴趣,那么您也可能对Callable和Runnable之间的区别感兴趣。
扩展线程和实现可运行的区别在于:
这将在Oracle的"定义和启动线程"教程中讨论:
Which of these idioms should you use? The first idiom, which employs a
Runnable object, is more general, because the Runnable object can
subclass a class other than Thread. The second idiom is easier to use
in simple applications, but is limited by the fact that your task
class must be a descendant of Thread. This lesson focuses on the first
approach, which separates the Runnable task from the Thread object
that executes the task. Not only is this approach more flexible, but
it is applicable to the high-level thread management APIs covered
later.
换句话说,实现
如果我没有错的话,它或多或少类似于
接口和抽象类有什么区别?
扩展建立"是"关系和接口提供"有"能力。
首选可运行的实现:
首选"延伸线":
一般来说,您不需要重写线程行为。因此,在大多数情况下,实现可运行是首选的。
另一方面,使用高级
看看这个问题:
执行器服务与临时线程生成器
最简单的解释是,通过实现
例如,假设有两个线程,thread1将一个整数放入数组中,thread2在数组填满时从数组中获取整数。注意,为了让thread2工作,它需要知道数组的状态,不管thread1是否已经填充了它。
实现
将线程类与可运行实现分离还可以避免线程和run()方法之间的潜在同步问题。单独的可运行代码通常在引用和执行可运行代码的方式上提供更大的灵活性。
我们是否可以重新审视我们希望我们班表现得像
如果这是整个目的,那么我在哪里看到一个专门线程的需求呢?这可以通过从系统的线程池中提取一个原始线程并将其分配给我们的任务(可能是类的一个实例)来实现,也就是说。
所以让我们遵守OOPS的概念,编写一个我们需要的类型的类。有很多方法可以做事情,以正确的方式做事情很重要。
我们需要一个任务,所以写一个可以在线程上运行的任务定义。所以使用runnable。
请记住,
我们不希望线程的属性,而是希望我们的类表现为一个可以运行的任务。
您希望实现一个接口而不是扩展一个基类的一个原因是您已经扩展了其他一些类。只能扩展一个类,但可以实现任意数量的接口。
如果您扩展线程,那么基本上就是防止您的逻辑被"this"以外的任何其他线程执行。如果您只需要一些线程来执行您的逻辑,那么最好只实现可运行的。
如果使用runnable,则可以节省空间以扩展到任何其他类。
对,如果调用threada调用,那么不需要调用start方法,run方法只在调用threada类之后调用。但如果使用threadb调用,则需要为call run方法提供启动线程。如果你还有什么帮助,请回答我。
对于大多数工作线程来说,最好的方法是将线程完全封装在工作类中,这样就不会有任何东西从外部干扰,并导致不需要的和无效的线程/类状态。
我刚刚发布了一个示例,因此我也将与您分享:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /** * 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(); } } |
把我的两分钱加在这里-尽可能使用
理想情况下,您不应该扩展线程类;
那些喜欢解谜的人可以看到延长线的另一个副作用。下面的代码将在无人通知时打印无法访问的代码。
请访问http://pastebin.com/bjknns2g。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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"); } } } |
我发现出于上述所有原因使用runnable是最有用的,但有时我喜欢扩展线程,这样我就可以创建自己的线程停止方法,并直接在我创建的线程上调用它。
Java不支持多个继承,因此如果扩展了线程类,则没有其他类将被扩展。
例如:如果您创建了一个applet,那么它必须扩展applet类,所以这里创建线程的唯一方法是实现可运行的接口
线程和可运行线程之间的差异。如果我们使用线程类创建线程,则线程数等于我们创建的对象数。如果我们是通过实现可运行的接口来创建线程,那么我们可以使用单个对象来创建多个线程。因此单个对象由多个线程共享,因此占用的内存更少。
因此,如果我们的数据不敏感,这取决于需求。所以它可以在多个线程之间共享,我们可以使用可运行的接口。
这是坚实的:单一的责任。
线程体现了一段代码异步执行的运行上下文(如执行上下文:堆栈帧、线程ID等)。理想情况下,这段代码应该是相同的实现,无论是同步的还是异步的。
如果在一个实现中将它们捆绑在一起,则会给结果对象两个不相关的更改原因:
如果您使用的语言支持部分类或多重继承,那么您可以在它自己的超级类中分离每个原因,但归根结底,这与组成两个对象是一样的,因为它们的特性集不重叠。这是理论上的。
在实践中,一般来说,一个程序不需要比必要的更复杂。如果有一个线程正在处理一个特定的任务,而从未更改过该任务,那么将这些任务划分为不同的类可能就没有意义了,而且代码仍然简单。
在Java的上下文中,由于该工具已经存在,因此很容易直接使用独立的EDCOX1×0类来启动,并将它们的实例传递给EDCOX1×1(或EDCOX1×2)实例。一旦习惯了这种模式,使用(甚至读取)并不比简单的可运行线程更困难。
实现可运行线程和扩展线程的一个区别是,通过扩展线程,每个线程都有一个与之关联的唯一对象,而实现可运行线程,许多线程可以共享同一个对象实例。
实现可运行的类不是线程,而是类。要让一个线程执行一个可运行的实例,您需要创建一个线程实例并将该可运行实例作为目标传入。
在大多数情况下,如果您只打算重写run()方法,而没有其他线程方法,那么应该使用可运行接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则类不应该被子类化。
当需要扩展超类时,实现可运行接口比使用线程类更合适。因为我们可以在实现可运行接口时扩展另一个类来生成线程。但如果我们只是扩展线程类,就不能从任何其他类继承。
线程保持不希望访问的行为;
- 它的同步锁用于连接等。
- 它有一些你可以偶然进入的方法。
但是,如果您的子类线程必须考虑实现更多的线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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(); } } |
如果你运行这个,你可能会期望
1 | id: 12345678, name: Hello World, state: testing |
但是,您没有调用您认为自己是的方法,因为您使用的是
1 | id: 11, name: Thread-0, state: RUNNABLE |
这可能不是一个答案,但无论如何,还有一种创建线程的方法:
在极少数情况下,你只运行一次,你应该延长线,因为干燥。如果您多次调用它,那么应该实现runnable,因为不应该重新启动同一个线程。
通过扩展线程类,派生类不能扩展任何其他基类,因为Java只允许单继承。相反,通过实现可运行接口,类仍然扩展其他基类。
The most significant difference between implementing Runnable and extending Thread is given below :
通过扩展线程,派生类本身就是一个线程对象,而实现可运行接口则将同一对象共享给多个线程。
简单的说法是:如果您实现了接口,这意味着您正在实现它的所有方法,并且如果您扩展了类,那么您将继承您选择的方法…在这种情况下,只有一个名为run()的方法,因此更好地实现可运行的接口。
thread类定义了几个方法,扩展类可以是
但是,
因为runnable是一个接口,所以可以扩展其他类。但如果您扩展线程,那么这个选项就不存在了。
如果您不修改或增强整个
线程和可运行的主要区别在于:-线程类似于:worker(execute runnable)-可运行的类似于:作业(由线程执行)
我会说实际任务与线程是分离的。在可运行的情况下,我们可以将任务传递给线程、执行器框架等,而在扩展线程时,任务与线程对象本身耦合。如果扩展线程,则无法执行任务隔离。这就像我们把任务烧成线程对象,就像集成电路芯片(更具体地说,它不能处理任何任务)。
你可以联合使用
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 } } |