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


    先来看下结论:正确启动线程的方式是使用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()方法。

  • 相关阅读:
    1077. 互评成绩计算 (20)
    1076. Wifi密码 (15)
    c语言之利用指针复制字符串的几种形式
    c语言之利用函数实现字符串的复制
    c语言之字符串中字符的存取方法
    E0144"const char *" 类型的值不能用于初始化 "char *" 类型的实体的三种解决方法
    c语言之使用指针*和地址&在二维数组中表示的含义
    c语言之指向二维数组元素的指针变量
    c语言之在函数内部改变数组的值从而影响外部数组的四种方式
    c语言之使用指针将数组进行反转
  • 原文地址:https://www.cnblogs.com/sgh1023/p/12241220.html
Copyright © 2020-2023  润新知