创建多线程的三种方式

在java中给我们提供了三种方式来创建多线程。前两种是我们比较常见的,第三种是JDK1.5之后提供给我们的。接下来我们详细的看一下这三种创建线程的写法。

继承Thread类

第一种方式是继承Thread类的写法,代码如下:

        Thread thread = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("线程创建的第一种方式:"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();

注意这里我们覆盖的是run方法,而不是start方法,并且也千万不要覆盖start方法。为什么不能覆盖start方法呢?我们看一下Thread源码中start方法是怎么写的:

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

注意这个方法是加锁的方法。在这个方法中最重要的一段代码是:start0();我们接着再来看一下start0()这个方法:

    private native void start0();

它是个native方法。就是这个native的start0()方法,它实现了启动线程,申请栈内存、运行run方法、修改线程状态等职责。线程管理和栈内存管理都是由jvm负责的。如果你覆盖了start方法,也就是撤销了线程管理和栈内存管理的能力,这样还如何启动一个线程呢?不过Thread的这个设计是很精妙的,因为你只需要关注你的业务逻辑就行了,而对于线程和栈内存的管理都有JVM来做就行了。如果在你的开发中不得不要覆盖start方法的话,请千万要记得调用super.start(),要不然你的线程无法启动。

实现Runnable接口

第二种创建线程的方式是实现Runnable接口。具体代码请看下面:

       Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("线程创建的第二种方式:"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread2.start();

这种写法是实现了Runnable接口的一种写法。它的原理是什么呢?我们来看一下Thread源码中run的写法:

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

在run方法中我们可以看到如果target != null就调用target.run()方法。而这个target是从哪儿来的呢?我们继续看Thread的源码,发现在init的方法中有这样一句话:

this.target = target;

接下来我们再看init()这个方法是在哪儿被调用的?通过翻读源码我们可以发现在Thread的每一个构造函数中都会调用init这个方法,并且有这样一个构造函数:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

看到了吧。我们就是在创建Thread的时候,通过Thread的构造函数传递进去的Runnable的实现类。而线程启动的时候,调用run方法,run方法又接着调用Runnable实现类的run方法!!!!

实现Callable接口

在JDK1.5之后又给我们提供了一种新的创建线程的方式:实现Callable方法。具体代码如下:

        FutureTask<Integer> ft = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int i = 0;
                for(;i<10;i++){
                    System.out.println("线程创建的第三种方式:"+Thread.currentThread().getName());
                }
                return i;
            }
        });
        new Thread(ft).start();

在上面的代码中,在创建FutureTask对象的时候,我们把Callable的一个匿名实现类当做参数传到了FutureTask的构造函数中,而在启动线程的时候,我们又把创建的FutureTask的对象当做参数传到了Thread的构造函数中。在这里请注意我们此时不是覆盖的run方法,而是一个叫call的方法。大家可能会感到疑惑这个FutureTask和Callable这两个到底是个什么玩意?下面我们一个一个的分析:

FutureTask

通过翻读FutureTask的源码我们可以看出来实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口。注意:这里是继承了两个接口!你可能会有疑问JAVA中不是没有多继承吗?不错,java中类是没有多继承的,而对于接口是有多继承的!!!到这里我们明白一件事,那就是FutureTask是Runnable接口的一个实现类。到这里你是不是明白了点什么呢?

public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> 

如果你不明白的话,那就多看几遍第二种创建线程的方式和它的原理吧。接下来我们来看一下FutureTask这个类中的run方法是怎么写的:

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();

上面这个方法中的代码我没有贴全,只贴出来了主要的部分。在这个方法中我们会发现这样的两句话Callable<V> c = callable;result = c.call();这两句话就是关键!!!通过翻读源码我们就会发现源码这个callable就是我们刚才传到FutureTask中的Callable的实现类啊!

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

c.call()那不就是调用的Callable实现类的call方法吗?!!!到这里终于真相大白了!FutureTask中其他方法有兴趣的同学可以继续研究一下。

Callable

在Callable这个接口中只有一个call方法。
总结

在实际编码中,我们看到创建线程更多的是使用第二种方式,因为它更符合java中面向接口编程的思想。
最后出个题考一下大家,请说出下面代码的运行结果:

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

推荐阅读更多精彩内容

  • 先看几个概念:线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程。多线程:解决多任务同时执行的需求,合理...
    yeying12321阅读 540评论 0 0
  • 一.线程与进程相关 1.进程   定义:进程是具有独立功能的程序关于某个数据集合上的一次运行活动,进程是操作系统分...
    Geeks_Liu阅读 1,711评论 2 4
  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,779评论 14 507
  • 线程概述 线程与进程 进程  每个运行中的任务(通常是程序)就是一个进程。当一个程序进入内存运行时,即变成了一个进...
    闽越布衣阅读 1,008评论 1 7
  • 一个朋友,好像从我有记忆开始他就在了,而且我们形影不离,我们对彼此的了解有些方面甚至超过家人(毕竟有些事情不能和家...
    ChuJimmy阅读 146评论 0 0