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

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


当前回答

故事的寓意:

仅当您想要覆盖某些行为时才继承。

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

继承更少,接口更多。

其他回答

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

例如:如果您创建一个applet,那么它必须扩展applet类,所以这里创建线程的唯一方法是实现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]

在极少数情况下,您只运行一次,应该因为DRY而扩展Thread。如果多次调用它,则应实现Runnable,因为不应重新启动同一线程。

我会说实际任务与线程分离。在Runnable的情况下,我们可以将任务传递给线程、执行器框架等,而通过扩展Thread任务则与线程对象本身耦合。在扩展线程的情况下,无法执行任务隔离。这就像我们将任务烧成线程对象,就像IC芯片一样(更具体地说,不会得到任务的任何句柄)。

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

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