>  기사  >  Java  >  Java에서 스레드를 시작하는 올바른 방법과 잘못된 방법 자세히 살펴보기

Java에서 스레드를 시작하는 올바른 방법과 잘못된 방법 자세히 살펴보기

coldplay.xixi
coldplay.xixi앞으로
2020-09-28 16:55:092056검색

Java에서 스레드를 시작하는 올바른 방법과 잘못된 방법 자세히 살펴보기

Java에서 스레드를 시작하는 올바른 방법과 잘못된 방법을 자세히 살펴보겠습니다.

이전 기사 검토

  1. Java에서 멀티스레딩을 구현하는 방법에는 몇 가지가 있는지 자세히 분석합니다. 본질)

start 메소드와 run 메소드 비교

코드 데모:

/**
 * <p>
 * start() 和 run() 的比较
 * </p>
 *
 * @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 메소드 사용 직접 새 스레드를 시작하지 않습니다. (잘못된 방법)run 方法不会启动一个新线程。(错误方式)

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

start 方法分析

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

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

    • 线程对象在初始化之后调用了 start 方法之后, 当前线程(通常是主线程)会请求 JVM 虚拟机如果有空闲的话来启动一下这边的这个新线程。
    • 也就是说, 启动一个新线程的本质就是请求 JVM 来运行这个线程。
    • 至于这个线程何时能够运行,并不是简单的由我们能够决定的,而是由线程调度器去决定的。
    • 如果它很忙,即使我们运行了 start 方法,也不一定能够立刻的启动线程。
    • 所以说 srtart 方法调用之后,并不意味这个方法已经开始运行了。它可能稍后才会运行,也很有可能很长时间都不会运行,比如说遇到了饥饿的情况。
    • 这也就印证了有些情况下,线程 1 先掉用了 start 方法,而线程 2 后调用了 start 方法,却发现线程 2 先执行线程 1 后执行的情况。
    • 总结: 调用 start 方法的顺序并不能决定真正线程执行的顺序。
    • 注意事项
      • start 方法会牵扯到两个线程。
      • 第一个就是主线程,因为我们必须要有一个主线程或者是其他的线程(哪怕不是主线程)来执行这个 start 方法,第二个才是新的线程。
      • 很多情况下会忽略掉为我们创建线程的这个主线程,不要误以为调用了 start 就已经是子线程去执行了,这个语句其实是主线程或者说是父线程来执行的,被执行之后才去创建新线程。
  • start 方法创建新线程的准备工作

    • 首先,它会让自己处于就绪状态。
      • 就绪状态指已经获取到除了 CPU 以外的其他资源, 如已经设置了上下文、栈、线程状态以及 PC(PC 是一个寄存器,PC 指向程序运行的位置) 等。
    • 做完这些准备工作之后,就万事俱备只欠东风了,东风就是 CPU 资源。
    • 做完准备工作之后,线程才能被 JVM 或操作系统进一步去调度到执行状态等待获取 CPU 资源,然后才会真正地进入到运行状态执行 run 方法中的代码。
  • 需要注意: 不能重复的执行 start 方法

    • 代码示例

      /**
      * <p>
      * 演示不能重复的执行 start 方法(两次及以上), 否则会报错
      * </p>
      *
      * @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&#39;s list of threads
     * and the group&#39;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 &#39;not yet started&#39;
 */private volatile int threadStatus = 0;复制代码

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

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 方法源码分析

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

对于 run 方法的两种情况

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

    🎜🎜start 메소드는 새 스레드를 시작합니다. (올바른 방법)🎜🎜

시작 방법 분석🎜

시작 방법 의미 및 주의사항

🎜 🎜🎜 start 메소드는 새 스레드를 시작할 수 있습니다. 🎜🎜🎜초기화 후 스레드 객체가 start 메서드를 호출한 후 현재 스레드(일반적으로 메인 스레드)는 JVM 가상 머신에 이 새 스레드가 사용 가능한 경우 시작하도록 요청합니다. 🎜🎜즉, 새 스레드 시작의 핵심은 JVM에 이 스레드를 실행하도록 요청하는 것입니다. 🎜🎜이 스레드가 언제 실행될 수 있는지는 단순히 우리가 결정하는 것이 아니라 스레드 스케줄러에 의해 결정됩니다. 🎜🎜매우 바쁜 경우 start 메소드를 실행하더라도 스레드를 즉시 시작하지 못할 수도 있습니다. 🎜🎜그래서 srtart 메서드가 호출된 후에 이 메서드가 실행되기 시작했다는 의미는 아닙니다. 나중에까지 실행되지 않을 수도 있고, 기아 등으로 오랫동안 실행되지 않을 수도 있습니다. 🎜🎜이것은 또한 어떤 경우에는 스레드 1이 먼저 start 메서드를 호출한 다음 스레드 2가 start 메서드를 호출하지만 스레드 2가 다음을 실행한다는 것을 증명합니다. 실행 후 처음 1을 스레드합니다. 🎜🎜요약: start 메서드가 호출되는 순서는 실제 스레드가 실행되는 순서를 결정하지 않습니다. 🎜🎜🎜Notes🎜🎜🎜start 메소드에는 두 개의 스레드가 포함됩니다. 🎜🎜이 start 메소드를 실행하려면 메인 스레드나 다른 스레드(메인 스레드가 아니더라도)가 있어야 하기 때문에 첫 번째는 메인 스레드이고, 두 번째는 새로운 스레드입니다. 실. 🎜🎜대부분의 경우 스레드를 생성하는 메인 스레드는 무시됩니다. start를 호출하면 이 명령문이 실제로는 하위 스레드에 의해 실행되었다는 의미로 잘못 생각하지 마십시오. 스레드 또는 상위 스레드를 실행하려면 실행 후 새 스레드가 생성됩니다. 🎜🎜🎜🎜🎜start 메서드를 사용하여 새 스레드 준비를 생성합니다. 🎜🎜🎜먼저 자체적으로 준비 상태가 됩니다. 🎜🎜준비 상태는 컨텍스트, 스택, 스레드 상태, PC(PC는 레지스터, PC는 프로그램이 실행 중인 위치를 가리킴) 등 CPU 이외의 다른 리소스를 얻었음을 의미합니다. . 🎜🎜🎜이러한 준비를 완료하면 모든 준비가 완료되었으며 CPU 리소스인 Dongfeng만 있으면 됩니다. 🎜🎜준비 작업을 완료한 후 스레드는 JVM 또는 운영 체제에 의해 실행 상태로 추가 예약되어 CPU 리소스를 기다린 다음 실제로 실행 상태로 들어가 run 메소드. 🎜🎜🎜🎜🎜참고: start 메소드는 반복적으로 실행할 수 없습니다.🎜🎜🎜🎜🎜코드 예🎜rrreee🎜🎜🎜오류 원인🎜🎜🎜<code>start 실행이 시작되면, 스레드 상태 Runnable과 같은 초기 New 상태에서 후속 상태로 들어가고 스레드가 실행을 완료하면 스레드는 종료된 상태가 되며 종료된 상태는 다시 돌아올 수 없으므로 위의 예외가 발생합니다. 즉, 초기 상태로 돌아갈 수 없다는 것입니다. 여기의 설명은 충분히 명확하지 않습니다. 더 자세히 이해하기 위해 소스 코드를 살펴보겠습니다. 🎜🎜🎜

메소드 소스 코드 분석 시작

소스 코드 h4>rrreee

소스 코드의 처리

🎜🎜1단계:🎜새 스레드를 시작할 때 먼저 스레드 상태가 초기 상태인지 확인하고, 이것이 이유 위에 예외가 발생한 이유입니다. 즉, 다음 코드는 🎜rrreee🎜threadStatus 변수에 다음과 같이 주석 처리되어 있습니다. 이는 Java 스레드 상태가 초기에 0(아직 시작되지 않음)으로 표시됨을 의미합니다.🎜rrreee🎜🎜두 번째 단계: 🎜스레드 그룹에 추가하세요. 즉, 다음 코드는 다음과 같습니다.🎜rrreee🎜🎜3단계:🎜마지막으로 이 기본 메서드를 start0() 호출합니다(네이티브는 해당 코드가 Java가 아니라 C/C++, 특히 구현은 JDK에서 볼 수 있습니다. 이해하세요), 즉 다음 코드입니다:🎜rrreee

실행 메서드 분석🎜

run 메소드 소스 코드 분석

rrreee

run 메소드의 두 가지 상황

🎜🎜🎜첫 번째: Thread 클래스 재작성 run 메서드를 사용하면 Threadrun 메서드가 유효하지 않으며 재정의된 run 메서드가 실행됩니다. 🎜
  • 두 번째: target 개체(즉, Runnable 인터페이스의 구현)를 전달하고 원래 <code>Thread 실행을 실행합니다. code> 그런 다음 메서드는 target 개체의 run 메서드를 실행합니다. target 对象(即 Runnable 接口的实现),执行 Thread 的原有 run 方法然后接着执行 target 对象的 run 方法。

  • 总结:

    • run 方法就是一个普通的方法, 上文中直接去执行 run 方法也就是相当于我们执行自己写的普通方法一样,所以它的执行线程就是我们的主线程。
    • 所以要想真正的启动线程,不能直接调用 run 方法,而是要调用 start 方法,其中可以间接的调用 run
  • 요약:
      run 메소드는 위의 run 메소드를 직접 실행하는 것은 우리가 직접 작성한 일반 메소드를 실행하는 것과 같습니다. 메소드는 동일하므로 실행 스레드가 메인 스레드입니다.

      그래서 실제로 스레드를 시작하려면 run 메서드를 직접 호출할 수 없고 start 메서드를 호출해야 합니다. >run 메소드를 간접적으로 실행합니다.

      관련 학습 권장사항: 🎜🎜🎜java 기본🎜🎜🎜🎜

    위 내용은 Java에서 스레드를 시작하는 올바른 방법과 잘못된 방법 자세히 살펴보기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제