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 接口。
网友评论