用代码说话:如何正确启动线程

先来看下结论:正确启动线程的方式是使用start()方法,而不是使用run()方法。

代码实战

1. 输出线程名称

“Talk is cheap. Show me the code”,用代码说话:分别调用run()方法和start()方法,打印输出线程的名字。

public class StartAndRunThread {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        runnable.run();
        new Thread(runnable).start();
    }
}

运行结果:


image.png

2. 深入一点

如果代码是这样的,执行结果有什么不同呢?

public class StartAndRunThread {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        runnable.run();
        new Thread(runnable).start();
        runnable.run();
    }
}

执行结果为:


image.png

是不是有点意外?然而,这就是真相。其实也不难解释。

  1. 我们说的并发是什么,并发不就是线程之间的运行互不干扰嘛?当JVM启动的时候,创建一个mian线程来运行main()方法。当执行到“new Thread(runnable).start();”的时候main线程会新建一个Thread-0线程。main线程和Thread-0线程的执行时互不相干的,所以可能不会出现“main-Thread-0-main”的结果。

  2. 我执行了n(n>20)次,运行结果依然如上图所示,没有出现“main-Thread-0-main”。这是为什么呢?回忆一下线程的生命周期, Java中,线程(Thread)定义了6种状态: NEW(新建)、RUNNABLE(可执行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(限时等待)、TERMINATED(结束)。当调用了start()方法之后,线程进入RUNNABLE状态,RUNNABLE的意思是可运行,即可能正在执行,也可能没有正在执行。那调用了start方法之后,什么时候执行呢?调用start()方法之后,我们只是告诉JVM去执行这个线程,至于什么时候运行是由线程调度器来决定的。从操作系统层面,其实调用start()方法之后要去获取操作系统的时间片,获取到才会执行。这个问题,可以对比思考“ thread.start()调用之后线程会立刻执行吗?”更多可以参考:从源码解读线程(Thread)和线程池(ThreadPoolExecutor)的状态

start()方法源码分析

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 */
        }
    }
}

可以看到,start()方法被synchronized关键字修饰,保证了线程安全。启动流程分为下面三个步骤:

  1. 首先会检查线程状态,只有threadStatus == 0(也就是线程处于NEW状态)状态下的线程才能继续,否则会抛出IllegalThreadStateException。

  2. 将线程加入线程组

  3. 调用native方法——start0()方法启动线程。

线程启动相关问题

1. 一个线程两次调用start()方法会出现什么情况?

会抛出IllegalThreadStateException,具体原因可以用源码和线程启动步骤进行说明。

2. 既然 start() 方法会调用 run() 方法,为什么我们选择调用 start() 方法,而不是直接调用 run() 方法呢?

start()才是真正启动一个线程,而如果直接调用run(),那么run()只是一个普通的方法而已,和线程的生命周期没有任何关系。用代码验证一下:

public class Main implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Main().run();
        new Thread(new Main()).start();
    }

}
image.png

在上面代码中,直接调用run()方法,run()只是一个普通的方法,由当前线程——main线程执行。start()才是真正启动一个线程——Thread0,run()方法由线程Thread0执行。

3. 上面说start()会调用run()方法,这个怎么证明?为什么在start()方法的源码中没有看到调用了run()方法?

可以看start()方法的注释部分:

/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception  IllegalThreadStateException  if the thread was already
*               started.
* @see        #run()
* @see        #stop()
*/

也就是说当该线程开始执行的时候,Java虚拟机会自动调用该线程的run()方法。

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

推荐阅读更多精彩内容