详细分析 Java 中启动线程的正确和错误方式

程序员考拉

共 4419字,需浏览 9分钟

 ·

2020-10-12 01:37


start 方法和 run 方法的比较


代码演示:


/**
 *


 * start() 和 run() 的比较
 *


 *
 * @author 踏雪彡寻梅
 * @version 1.0
 * @date 2020/9/20 - 16:15
 * @since JDK1.8
 */

public class StartAndRunMethod {
    public static void main(String[] args) {
        // run 方法演示
        // 输出: name: main
        // 说明由主线程去执行的, 不符合新建一个线程的本意
        Runnable runnable = () -> {
            System.out.println("name: " + Thread.currentThread().getName());
        };
        runnable.run();

        // start 方法演示
        // 输出: name: Thread-0
        // 说明新建了一个线程, 符合本意
        new Thread(runnable).start();
    }
}


从以上示例可以分析出以下两点:


  • 直接使用 run 方法不会启动一个新线程。(错误方式)

  • start 方法会启动一个新线程。(正确方式)


start 方法分析


start 方法的含义以及注意事项


start 方法可以启动一个新线程。


第一个就是主线程,因为我们必须要有一个主线程或者是其他的线程(哪怕不是主线程)来执行这个 start 方法,第二个才是新的线程。


很多情况下会忽略掉为我们创建线程的这个主线程,不要误以为调用了 start 就已经是子线程去执行了,这个语句其实是主线程或者说是父线程来执行的,被执行之后才去创建新线程。


线程对象在初始化之后调用了 start 方法之后, 当前线程(通常是主线程)会请求 JVM 虚拟机如果有空闲的话来启动一下这边的这个新线程。


也就是说, 启动一个新线程的本质就是请求 JVM 来运行这个线程。


至于这个线程何时能够运行,并不是简单的由我们能够决定的,而是由线程调度器去决定的。


如果它很忙,即使我们运行了 start 方法,也不一定能够立刻的启动线程。


所以说 srtart 方法调用之后,并不意味这个方法已经开始运行了。它可能稍后才会运行,也很有可能很长时间都不会运行,比如说遇到了饥饿的情况。


这也就印证了有些情况下,线程 1 先掉用了 start 方法,而线程 2 后调用了 start 方法,却发现线程 2 先执行线程 1 后执行的情况。


总结: 调用 start 方法的顺序并不能决定真正线程执行的顺序。


注意事项


start 方法会牵扯到两个线程。


第一个就是主线程,因为我们必须要有一个主线程或者是其他的线程(哪怕不是主线程)来执行这个 start 方法,第二个才是新的线程。


很多情况下会忽略掉为我们创建线程的这个主线程,不要误以为调用了 start 就已经是子线程去执行了,这个语句其实是主线程或者说是父线程来执行的,被执行之后才去创建新线程。


需要注意: 不能重复的执行 start 方法


代码示例


/**
*


* 演示不能重复的执行 start 方法(两次及以上), 否则会报错
*


*
* @author 踏雪彡寻梅
* @version 1.0
* @date 2020/9/20 - 16:47
* @since JDK1.8
*/

public class CantStartTwice {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println("name: " + Thread.currentThread().getName());
        };
        Thread thread = new Thread(runnable);
        // 输出: name: Thread-0
        thread.start();
        // 输出: 抛出 java.lang.IllegalThreadStateException
        // 即非法线程状态异常(线程状态不符合规定)
        thread.start();
    }
}


报错的原因


start 一旦开始执行,线程状态就从最开始的 New 状态进入到后续的状态,比如说 Runnable,然后一旦线程执行完毕,线程就会变成终止状态,而终止状态永远不可能再返回回去,所以会抛出以上异常,也就是说不能回到初始状态了。这里描述的还不够清晰,让我们来看看源码能了解的更透彻。


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 方法
        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 */

        }
    }
}


源码中的流程


第一步:


启动新线程时会首先检查线程状态是否为初始状态, 这也是以上抛出异常的原因。即以下代码:


if (threadStatus != 0)
  throw new IllegalThreadStateException();


其中 threadStatus 这个变量的注释如下,也就是说 Java 的线程状态最初始(还没有启动)的时候表示为 0:


/* Java thread status for tools,
 * initialized to indicate thread 'not yet started'
 */


第二步:


将其加入线程组。即以下代码:


group.add(this);


第三步:


最后调用
start0() 这个 native 方法(native 代表它的代码不是由 Java 实现的,而是由 C/C++ 实现的,具体实现可以在 JDK 里面看到,了解即可), 即以下代码:


boolean started = false;
try {
    // 第三步, 调用 start0 方法
    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 */

    }
}


run 方法分析


run 方法源码分析


@Override
public void run() {
    // 传入了 target 对象(即 Runnable 接口的实现), 执行传入的 target 对象的 run 方法
    if (target != null) {
        target.run();
    }
}


对于 run 方法的两种情况


第一种: 重写了 Thread 类的 run 方法,Threadrun 方法会失效, 将会执行重写的 run 方法。


第二种: 传入了 target 对象(即 Runnable 接口的实现),执行 Thread 的原有 run 方法然后接着执行 target 对象的 run 方法。



总结:


run 方法就是一个普通的方法, 上文中直接去执行 run 方法也就是相当于我们执行自己写的普通方法一样,所以它的执行线程就是我们的主线程。


所以要想真正的启动线程,不能直接调用 run 方法,而是要调用 start 方法,其中可以间接的调用 run 方法。


原文链接:cnblogs.com/txxunmei/p/13747631.html



浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报