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?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342