Thead 与 Runnable

Thread 与 Runnable 区别

Thread 是 Java 语言中对于系统线程的一个抽象,而 Runnable 在 Java 语言中对于线程运行时需要执行任务的一个抽象。

网上有诸如创建线程的三种方式(继承 Thread、实现 Runnable、实现 Callable 或 Future),我认为这种说法是不对的。网上所说的这三种方式的后两者创建的并不是线程,而是线程运行时需要执行的任务。

Callable 在内部处理时,会包装成 FutureTask 对象,而这个 FutureTask 实际上还是个 Runnable :P

从 POSIX 线程标准 API 中可以很明确地看出:

int pthread_create(
    pthread_t *new_thread_ID,
    const pthread_attr_t *attr,
    void * (*start_func)(void *),
    void *arg
);

其中第三个参数才是新线程启动时调用的函数名,而这个函数是可以通过设置参数的,这第三个参数就相当于 Java 中的 Runnable。如果把 Runnable 对象也作为线程的话,那 POSIX 中的 start_func 也可以理解成线程了,这很明显是不正确的。

窃以为,在 Java 中创建线程的方式只有一种,就是通过 Thread 的构造方法然后再调用 start 方法来实现。

为什么 Thread 要实现 Runnable 接口?

网上很多文章称,是由于 Thread 中的 run 方法需要被系统调用,其实并不然,Thread#start 的 native 方法最终调用 run 是直接在 Thread 上进行的,并不需要判断其是否是 Runnable 接口中的 run。而且这个 run 方法看似也违背 Thread 类的职责边界,这个 run 是系统内部调用的,更适合改为 private 修饰更佳。

但 Java 为什么要让 Thread 实现 Runnable 接口呢?实际上根本原因是“向下兼容”。

因为 Thread 和 Runnable 是 JDK 1.0 中增加的,但在 JDK 1.0 中不支持匿名的内部类语法,并不能像以下代码那样通过匿名内部类来构造 Thread 对象:

Thread thread = new Thread(new Runnable() {
    public void run() {
        // do something
    }
});

而在 JDK 1.0 时常用的作法,以及一些很老的教科书中是通过继承 Thread 类重写 run 方法来创建一个线程和一个线程任务。当初 Thread 设计时实现了 Runnable 接口也是基于此考虑的,便于实现。

在 JDK 1.1 中增加了匿名内部类的语法,更优的方式是通过上述代码匿名类来构造线程对象,而不应该通过继承 Thread 类来构建。但 Java 需要做到版本的向下兼容,因此并不能将 Thread 的 Runnable 去除,否则老的代码就无法编译或者运行了。

Thread 与 Executor

Thread 类中的对于线程任务的处理非常单一,也不便于扩展。而在 JDK 1.5 中增加的 Executor 接口,接口中只有一个接受 Runnable 参数的 execute 方法,线程任务与任务运行机制分离的接口,其是对线程执行更高层的抽象。

在 Java 中依赖接口比依赖具体的实现要更优:

  • 若依赖 Thread 对象,Thread 对象 start 操作内部基本上由 native 代码实现的,那这一块今后的扩展就比较差
  • 若依赖 Executor 接口,那具体的实现可以进行扩展,比如,我现在是一个线程任务一个线程去处理的,若今后发现这样太消耗系统线程资源了,那只要将 Executor 的实现给修改就好了,根据面向对象的里氏替换原则,其他地方我们是不需要作任何修改的。

通过 Executor 接口我们可以很方便地实现每一个任务都由一个新的线程去执行(取代 Thread 的构造):

public class NewThreadExecutor implements Executor {

    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }
}

甚至是可以不启线程运行,通过调用者线程同步运行:

public class SyncExecutor implements Executor {

    @Override
    public void execute(Runnable command) {
        command.run();
    }
}

当然了,通过线程池来执行就更不用说了,JDK 1.5 中自己就扩展了 Executor 实现了一个 ThreadPoolExecutorService。

所以,我们在我们的代码中应该尽可能地避免直接使用 Thread,而应该更优先考虑使用 Executor 接口。

主要参考

  1. Why does Thread implement Runnable?

你可能感兴趣的:(Thead 与 Runnable)