从我在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,我们可以将同一个对象分配给多个线程,并且每个线程共享相同的对象状态和行为。

例如,假设有两个线程,thread1在数组中放入一个整数,thread2在数组填满时从数组中取出整数。请注意,为了让thread2正常工作,它需要知道数组的状态,不管thread1是否已将其填满。

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

其他回答

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

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

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

随着Java8的发布,现在有了第三个选项。

Runnable是一个函数接口,这意味着可以使用lambda表达式或方法引用创建它的实例。

您的示例可以替换为:

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

或者如果要使用ExecutorService和方法引用:

executor.execute(runner::run)

这些不仅比示例要短得多,而且还具有其他答案中所述的使用Runnable over Thread的许多优点,例如单一责任和使用组合,因为您没有专门处理线程的行为。如果您只需要一个Runnable,那么这种方法也可以避免创建一个额外的类。

在这里加上我的两分钱-始终尽可能使用机具Runnable。以下是关于为什么不应该使用扩展线程

理想情况下,您不应该扩展Thread类;线程类应为最终类。至少它的方法像thread.getId()。有关扩展线程的错误,请参阅本讨论。那些喜欢解谜的人可以看到扩展线程的另一个副作用。以下代码将在无人通知时打印无法访问的代码。

请参见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 ");
        }
    } 
}

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

如果这就是全部目的,那么我在哪里看到需要专门的线程。这可以通过从系统的线程池中提取一个RAW线程并分配给它我们的任务(可能是我们类的一个实例)来完成,就是这样。

因此,让我们遵循OOP概念,编写一个所需类型的类。做事有很多方法,用正确的方式做事很重要。

我们需要一个任务,所以写一个可以在线程上运行的任务定义。所以使用Runnable。

始终记住,机具专门用于传递行为,扩展用于传递特性/属性。

我们不希望线程的属性,而是希望我们的类作为一个可以运行的任务。