从我在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
这两个代码块有什么显着差异吗?
是的:实施Runnable
是首选的方式,IMO.你并不是真正专注于线程的行为.你只是给它一些东西来运行.这意味着构图是哲学上 "更纯粹"的方式.
在实践方面,它意味着你可以实现Runnable
从另一个类扩展为好.
tl; dr:实现Runnable更好.但是,警告很重要
一般来说,我建议使用类似的东西,Runnable
而不是Thread
因为它允许你只是松散地将你的工作与你选择的并发性相结合.例如,如果你使用a Runnable
并在稍后决定这实际上并不需要它自己Thread
,你可以调用threadA.run().
警告:在这里,我强烈反对使用原始线程.我更喜欢使用Callables和FutureTasks(来自javadoc:"可取消的异步计算").超时,正确取消和现代并发支持的线程池的集成对我来说比成堆的原始线程更有用.
后续:有一个FutureTask
构造函数允许您使用Runnables(如果这是您最熟悉的)并且仍然可以获得现代并发工具的好处.引用javadoc:
如果您不需要特定结果,请考虑使用以下形式的结构:
Future> f = new FutureTask
所以,如果我们runnable
用你的替换他们threadA
,我们得到以下内容:
new FutureTask
允许您更接近Runnables的另一个选项是ThreadPoolExecutor.您可以使用execute方法传入Runnable以在将来某个时间执行"给定任务".
如果您想尝试使用线程池,上面的代码片段将变为类似以下内容(使用Executors.newCachedThreadPool()工厂方法):
ExecutorService es = Executors.newCachedThreadPool(); es.execute(new ThreadA());
故事的道德启示:
仅在您要覆盖某些行为时继承.
或者更确切地说,它应该被理解为:
继承少,界面更多.
那么多好的答案,我想在此添加更多.这有助于理解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实例并将其自身作为目标传递.
在大多数情况下,如果您只计划覆盖该run()
方法而不使用其他Thread方法,则应使用Runnable接口.这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应对类进行子类化.
当需要扩展超类时,实现Runnable接口比使用Thread类更合适.因为我们可以在实现Runnable接口的同时扩展另一个类来创建一个线程.
我希望这个能帮上忙!
我还没有提到一件令人惊讶的事情是,实施Runnable
使你的课程变得更加灵活.
如果你扩展线程,那么你正在做的动作总是在一个线程中.但是,如果你实现Runnable
它不一定是.您可以在一个线程中运行它,或者将它传递给某种执行器服务,或者只是作为单个线程应用程序中的任务传递它(可能在以后运行,但在同一个线程内).如果您使用的Runnable
话,选项会比您自己绑定的要多得多Thread
.
如果你想实现或扩展任何其他类,那么Runnable
如果你不希望任何其他类扩展或实现那么接口是最优选的,那么Thread
类更可取
最常见的区别是
当你extends Thread
上课时,之后你不能扩展你需要的任何其他课程.(如您所知,Java不允许继承多个类).
当您implements Runnable
,您可以为您的班级节省空间,以便将来或现在扩展任何其他课程.
Java不支持多继承,这意味着您只能在Java中扩展一个类,因此一旦扩展了Thread类,您就失去了机会,无法在Java中扩展或继承另一个类.
在面向对象的编程中,扩展类通常意味着添加新功能,修改或改进行为.如果我们没有在Thread上进行任何修改,那么请改用Runnable接口.
Runnable接口表示可以由普通线程或执行器或任何其他方式执行的任务.所以将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
,它将同一个对象共享给多个线程.
其实,这不是明智的比较Runnable
和Thread
互相.
这两者在多线程中具有依赖性和关系,就像Wheel and Engine
机动车的关系一样.
我想说,只有一种方法可以通过两个步骤实现多线程.让我说明一下.
Runnable:
实现interface Runnable
它意味着你正在创建一个run able
在不同线程中的东西.现在创建可以在线程内运行的东西(在线程内部可运行)并不意味着创建一个Thread.
所以这个类MyRunnable
只不过是带有void run
方法的普通类.它的对象将是一些普通的对象,只有一个方法run
在被调用时会正常执行.(除非我们在一个线程中传递对象).
线程:
class Thread
,我想说一个非常特殊的类,它具有启动一个新线程的能力,它实际上可以通过它的start()
方法实现多线程.
比较为什么不明智?
因为我们需要它们用于多线程.
对于多线程,我们需要两件事:
可以在Thread(Runnable)中运行的东西.
可以启动新线程(线程)的东西.
因此从技术上和理论上讲,它们都是启动螺纹所必需的,一个将运行,一个将使其运行(如Wheel and Engine
机动车辆).
这就是为什么你不能启动一个线程,MyRunnable
你需要将它传递给一个实例Thread
.
但是有可能只使用class Thread
因为类Thread
实现而创建和运行一个线程,Runnable
所以我们都知道Thread
也是一个Runnable
内部.
最后Thread
,Runnable
它们是多线程而不是竞争对手或替代品的补充.
您应该实现Runnable,但如果您在Java 5或更高版本上运行,则不应该new Thread
使用它来启动它,而是使用ExecutorService.有关详细信息,请参阅:如何在Java中实现简单线程.
我不是专家,但我可以想到实现Runnable而不是扩展Thread的一个原因:Java只支持单继承,所以你只能扩展一个类.
编辑:这最初说"实现一个接口需要更少的资源".同样,但你需要创建一个新的Thread实例,所以这是错误的.
我想说还有第三种方式:
public class Something { public void justAnotherMethod() { ... } } new Thread(new Runnable() { public void run() { instanceOfSomething.justAnotherMethod(); } }).start();
也许这会受到我最近大量使用Javascript和Actionscript 3的影响,但这样你的类就不需要实现一个非常模糊的界面了Runnable
.
随着Java 8的发布,现在有第三种选择.
Runnable
是一个功能接口,这意味着可以使用lambda表达式或方法引用创建它的实例.
您的示例可以替换为:
new Thread(() -> { /* Code here */ }).start()
或者如果您想使用an ExecutorService
和方法引用:
executor.execute(runner::run)
这些不仅比您的示例短得多,而且还具有其他使用Runnable
过的答案中所述的许多优点Thread
,例如单一责任和使用组合,因为您没有专门化线程的行为.如果您需要的所有内容Runnable
与示例中一样,这种方式也可以避免创建额外的类.
实例化一个接口可以更清晰地分离代码和线程的实现,所以我更喜欢在这种情况下实现Runnable.
这里的每个人似乎都认为实现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.(DebuggableThread.java:14) at DebuggableThread.main(DebuggableThread.java:19) ,5,main]
Runnable因为:
为Runnable实现提供了更大的灵活性来扩展另一个类
将代码与执行分开
允许您从线程池,事件线程或将来以任何其他方式运行runnable.
即使你现在不需要这些,也可能在将来.由于重写Thread没有任何好处,Runnable是一个更好的解决方案.
由于这是一个非常受欢迎的主题,而且好的答案遍布各处并深入处理,我觉得将其他人的好答案汇编成更简洁的形式是合理的,因此新人有一个简单的概述:
您通常会扩展一个类来添加或修改功能.所以,如果你不想来覆盖任何线程的行为,然后使用Runnable接口.
在相同的光,如果你不需要来继承 Thread方法,你可以做而没有开销使用Runnable接口.
单继承:如果扩展Thread,则无法从任何其他类扩展,因此如果您需要这样做,则必须使用Runnable.
将域逻辑与技术手段分开是一种很好的设计,从这个意义上说,让Runnable任务将你的任务与你的跑步者隔离开来是更好的选择.
您可以多次执行相同的Runnable 对象,但是,Thread对象只能启动一次.(也许是原因,为什么Executors接受Runnables,但不接受Threads.)
如果您将任务开发为Runnable,那么您现在和将来都可以灵活使用它.您可以通过Executors同时运行它,也可以通过Thread运行它.而你仍然可以在同一个线程中非同时使用/调用它,就像任何其他普通类型/对象一样.
这使得它也更容易分离任务的逻辑和并发性的方面你的单元测试.
如果您对此问题感兴趣,您可能也对Callable和Runnable之间的区别感兴趣.
扩展线程和实现Runnable之间的区别是:
这在Oracle的定义和启动线程教程中讨论:
你应该使用哪些成语?使用Runnable对象的第一个习惯用法更为通用,因为Runnable对象可以继承Thread以外的类.第二个习惯用法在简单的应用程序中更容易使用,但受限于你的任务类必须是Thread的后代这一事实.本课重点介绍第一种方法,该方法将Runnable任务与执行任务的Thread对象分开.这种方法不仅更灵活,而且适用于后面介绍的高级线程管理API.
换句话说,实现Runnable
将在您的类扩展类以外的类的情况下工作Thread
.Java不支持多重继承.此外,Thread
使用某些高级线程管理API时无法进行扩展.Thread
优选扩展的唯一方案是在一个小的应用程序中,将来不会更新.实现几乎总是更好,Runnable
因为随着项目的增长它更灵活.设计更改不会产生重大影响,因为您可以在java中实现许多接口,但只扩展一个类.
如果我没有错,它或多或少类似于
接口和抽象类之间有什么区别?
extends建立" Is A "关系和接口提供" Has a "能力.
首选实现Runnable:
如果您不必扩展Thread类并修改Thread API的默认实现
如果您正在执行fire and forget命令
如果您已经在扩展另一个班级
首选" 扩展线程 ":
如果必须覆盖oracle文档页面中列出的任何这些Thread方法
通常,您不需要重写Thread行为.因此,大多数情况下,实现Runnable是首选.
另外,使用高级ExecutorService
或ThreadPoolExecutorService
API可提供更多灵活性和控制.
看看这个SE问题:
ExecutorService与Casual Thread Spawner
最简单的解释是通过实现Runnable
我们可以将同一个对象分配给多个线程,并且每个线程Thread
共享相同的对象状态和行为.
例如,假设有两个线程,thread1在数组中放入一个整数,thread2在数组填满时从数组中取整数.请注意,为了使thread2工作,它需要知道数组的状态,thread1是否填充了它.
实现Runnable
允许您具有共享对象的这种灵活性,extends Thread
而使您为每个线程创建新对象,因此thread1完成的任何更新都会丢失给thread2.
将Thread类与Runnable实现分开还可以避免线程和run()方法之间潜在的同步问题.单独的Runnable通常在引用和执行可运行代码的方式上提供更大的灵活性.
这就是小号的SOLID:单一职责。
甲螺纹体现了运行的历境(如在执行上下文:堆栈帧,线程ID等)的异步执行一段代码的。这一段代码,理想情况下应该是相同的实现,无论是同步或异步的。
如果将它们捆绑在一起在一个实现中,则会为生成的对象提供两个无关的更改原因:
应用程序中的线程处理(即查询和修改执行上下文)
由一段代码(可运行部分)实现的算法
如果您使用的语言支持部分类或多重继承,那么您可以将每个原因都隔离在其自己的超类中,但是归结为与组成两个对象相同,因为它们的功能集不重叠。这是理论上的。
实际上,一般而言,程序不需要携带不必要的复杂性。如果您有一个线程在执行一项特定任务,而又从未更改过该任务,那么将这些任务划分为单独的类可能没有任何意义,并且您的代码将更加简单。
在Java的上下文中,由于该功能已经存在,因此直接从独立的Runnable
类开始并将它们的实例传递给Thread
(或Executor
)实例可能更容易。一旦习惯了这种模式,使用(甚至读取)就不会比简单的可运行线程的情况更难。
您希望实现接口而不是扩展基类的一个原因是您已经扩展了其他类.您只能扩展一个类,但可以实现任意数量的接口.
如果你扩展Thread,你基本上阻止你的逻辑被"this"之外的任何其他线程执行.如果您只想要一些线程来执行您的逻辑,那么最好只实现Runnable.
如果您使用runnable,则可以节省空间以扩展到任何其他类.
我们可以重新访问我们希望班级表现为什么的基本原因Thread
吗?没有任何理由,我们只是想执行一个任务,很可能是在异步模式下,这正是意味着任务的执行必须从我们的主线程和主线程分支,如果提前完成,可能会或可能不会等待对于分支路径(任务).
如果这是整个目的,那么我在哪里可以看到需要专门的线程.这可以通过从系统的线程池中获取RAW线程并为其分配我们的任务(可能是我们类的一个实例)来实现,就是这样.
因此,让我们遵守OOP概念并编写一个我们需要的类.有很多方法可以做到,以正确的方式做事很重要.
我们需要一个任务,所以编写一个可以在Thread上运行的任务定义.所以使用Runnable.
永远记住implements
是专门用于传授行为并extends
用于传授特征/属性.
我们不想要线程的属性,而是希望我们的类可以作为可以运行的任务来运行.