从我在Java中使用线程的时间来看,我发现了两种编写线程的方法:

使用可运行的机具:

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

或者,使用扩展线程:

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

这两个代码块有什么显著的区别吗?


当前回答

实际上,将Runnable和Thread相互比较是不明智的。

这两者在多线程中具有相关性和关系,就像机动车的车轮和发动机关系一样。

我想说,多线程只有一种方法,有两个步骤。让我表明我的观点。

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

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

为什么不明智地进行比较?因为我们需要这两个线程来实现多线程。

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

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

因此,从技术上和理论上来说,这两个都是启动线程所必需的,一个线程将运行,另一个线程会使线程运行(就像机动车的车轮和发动机)。

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

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

最后,Thread和Runnable是多线程的补充,而不是竞争对手或替代者。

其他回答

如果你想分开你的关注点,比如,如果你想创建任务,而谁是处理你任务的工作人员对你来说无关紧要,那么使用Runnable并将你的工作定义为任务。就像使用executor服务一样,您以Runnable/callable的形式定义任务,并将它们交给executor,然后由executor负责。

如果您想自己创建一个工作线程并将任务分配给该工作线程,那么您需要对工作线程进行更多的控制,然后使用thread类。

Thread和Runnable的主要区别在于:-线程类似于:Worker(执行Runnable)-可运行的类似于:作业(由线程执行)

这里的每个人似乎都认为实现Runnable是一条路,我并不真的不同意他们的观点,但在我看来,扩展Thread也是有道理的,事实上,您已经在代码中演示了这一点。

如果您实现Runnable,那么实现Runnable的类对线程名称没有控制权,调用代码可以设置线程名称,如下所示:

new Thread(myRunnable,"WhateverNameiFeelLike");

但是如果您扩展了Thread,那么您可以在类本身中管理它(就像在您的示例中,您将线程命名为“ThreadB”)。在这种情况下,您:

A) 可能会为调试目的提供一个更有用的名称

B) 强制将该名称用于该类的所有实例(除非您忽略了它是一个线程的事实,并将其作为Runnable来执行上述操作,但我们在这里讨论的是约定,所以可以忽略我认为的可能性)。

例如,您甚至可以对其创建进行堆栈跟踪,并将其用作线程名称。这可能看起来很奇怪,但取决于代码的结构,它对调试非常有用。

这看起来可能是一件小事,但如果你有一个非常复杂的应用程序,其中有很多线程,并且突然“停止”了(可能是因为死锁,也可能是因为网络协议中的一个不太明显的缺陷,或者其他无休止的原因),然后从Java获取堆栈转储,其中所有线程都被称为“线程-1”、“线程-2”、,“Thread-3”并不总是非常有用(这取决于线程的结构,以及您是否可以仅通过它们的堆栈跟踪来有效地区分哪个线程是哪个线程-如果您使用的是多个线程组,它们都运行相同的代码,则不总是可能的)。

尽管如此,您当然也可以通过创建线程类的扩展来以通用的方式执行上述操作,该扩展将其名称设置为其创建调用的堆栈跟踪,然后将其用于Runnable实现而不是标准的java线程类(见下文),但是除了堆栈跟踪之外用于调试的线程名称(对它可能处理的许多队列或套接字之一的引用,例如,在这种情况下,您可能更希望专门针对这种情况扩展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]

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

如果扩展Thread,基本上就是防止逻辑被除“this”之外的任何其他线程执行。如果您只希望一些线程执行您的逻辑,那么最好只实现Runnable。

有一件事我很惊讶,还没有提到,那就是实现Runnable使您的类更加灵活。

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