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

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


当前回答

这在Oracle的“定义和启动线程”教程中进行了讨论:

你应该使用这些成语中的哪一个?第一个成语,使用Runnable对象更一般,因为Runnable对象可以子类线程以外的类。第二个成语更容易使用在简单的应用程序中,但受限于您的任务类必须是线程的后代。本课重点介绍第一节方法,它将Runnable任务与Thread对象分离执行任务的。这种方法不仅更加灵活,而且它适用于所涵盖的高级线程管理API后来

换句话说,实现Runnable将在类扩展线程以外的类的情况下工作。Java不支持多重继承。此外,在使用某些高级线程管理API时,无法扩展线程。扩展Thread的唯一方案是在一个将来不会更新的小应用程序中。实现Runnable几乎总是更好的,因为随着项目的增长,它更加灵活。设计更改不会产生重大影响,因为您可以在java中实现许多接口,但只能扩展一个类。

其他回答

线程保持不打算访问的行为;

它的同步锁用于连接等。它有一些你可以偶然访问的方法。

但是,如果您的子类Thread必须考虑实现更多Thread。

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();
    }
}

如果你运行这个,你可能会期望

id: 12345678, name: Hello World, state: testing

然而,您并没有调用您认为是的方法,因为您使用的是Thread而不是ThreadMain中的方法,相反,您会看到类似

id: 11, name: Thread-0, state: RUNNABLE

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

实例化接口在代码和线程的实现之间提供了更清晰的分离,因此在这种情况下我更希望实现Runnable。

对于大多数工作线程来说,最好的方法是将线程完全封装在工作线程类中,这样就不会有任何外部干扰,导致不需要的和无效的线程/类状态。

我刚刚发布了一个示例,因此我也将与您分享:

/**
 * 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();
    }

}

最简单的解释是通过实现Runnable,我们可以将同一个对象分配给多个线程,并且每个线程共享相同的对象状态和行为。

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

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