杰瑞科技汇

java线程start和run

  • start(): 真正启动一个新线程,JVM 会在该线程中调用 run() 方法。
  • run(): 只是一个普通的方法,如果直接调用它,它不会创建新线程,而是在当前调用线程中顺序执行。

详细对比

为了更清晰地理解,我们从多个维度来对比这两个方法。

java线程start和run-图1
(图片来源网络,侵删)
特性 start() 方法 run() 方法
功能 启动一个新线程,并使其进入就绪状态。 定义线程要执行的任务,是一个普通的实例方法。
是否创建新线程 ,调用 start() 会创建一个新的线程栈。 ,直接调用 run() 只是在当前线程的栈中执行一个普通方法。
执行方式 异步执行,调用 start() 的线程会立即继续执行后续代码,而新线程会并发执行 run() 中的任务。 同步执行,调用 run() 的线程会阻塞,直到 run() 方法中的所有代码执行完毕。
调用次数 一个线程对象只能调用一次,多次调用会抛出 IllegalThreadStateException 可以调用多次,它只是一个普通方法,没有次数限制。
与 Thread 的关系 java.lang.Thread 类的方法,专门用于线程的启动。 java.lang.Runnable 接口中的方法,Thread 类实现了它。

代码示例与结果分析

让我们通过一个具体的例子来直观地感受它们的区别。

代码

class MyTask implements Runnable {
    @Override
    public void run() {
        // 获取当前正在执行代码的线程对象
        Thread currentThread = Thread.currentThread();
        System.out.println("run() 方法被线程 " + currentThread.getName() + " 执行。");
        for (int i = 0; i < 5; i++) {
            System.out.println(currentThread.getName() + " 正在运行: " + i);
            try {
                // 休眠100毫秒,模拟耗时操作
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class StartVsRunDemo {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread thread = new Thread(task, "My-New-Thread");
        System.out.println("--- 演示 start() ---");
        // 使用 start() 启动线程
        thread.start(); 
        System.out.println("main 线程继续执行,它不会等待 My-New-Thread 完成。");
        System.out.println("\n--- 演示 run() ---");
        // 直接调用 run() 方法
        thread.run(); 
        System.out.println("main 线程在 run() 方法执行完毕后才继续执行。");
    }
}

可能的输出结果

注意:由于线程的执行顺序是不确定的,start() 部分的输出顺序可能会略有不同,但 run() 部分的输出顺序是固定的。

--- 演示 start() ---
main 线程继续执行,它不会等待 My-New-Thread 完成。
run() 方法被线程 My-New-Thread 执行。
My-New-Thread 正在运行: 0
My-New-Thread 正在运行: 1
My-New-Thread 正在运行: 2
My-New-Thread 正在运行: 3
My-New-Thread 正在运行: 4
--- 演示 run() ---
run() 方法被线程 main 执行。
main 正在运行: 0
main 正在运行: 1
main 正在运行: 2
main 正在运行: 3
main 正在运行: 4
main 线程在 run() 方法执行完毕后才继续执行。

结果分析

  1. start() 的部分:

    • 当我们调用 thread.start() 后,main 线程的 System.out.println(...) 立即被执行了,这说明 start() 是异步的,main 线程没有阻塞。
    • 我们看到 run() 方法的内容是由名为 "My-New-Thread" 的线程执行的,这证明一个新的线程确实被创建并启动了。
    • "My-New-Thread" 的打印输出和 "main" 线程的打印输出是交错进行的,这体现了多线程的并发特性。
  2. run() 的部分:

    java线程start和run-图2
    (图片来源网络,侵删)
    • 当我们调用 thread.run() 后,可以看到 main 线程必须等待 run() 方法中所有的 for 循环和 sleep 都执行完毕后,才执行最后的 System.out.println(...),这说明 run() 是同步的。
    • 更重要的是,我们看到 run() 方法的内容是由名为 "main" 的线程执行的,这说明没有任何新线程被创建,它只是 main 线程调用了 task 对象的一个普通方法而已。

底层原理(为什么会有这种区别?)

这要从 Thread 类的源码和 JVM 的机制说起。

  1. start() 方法做了什么?

    • start() 方法会进行一些检查(比如线程是否已经启动过)。
    • 它会调用一个本地方法 native void start0(),这个方法是用 C++ 实现的,由 JVM 调用。
    • start0() 的核心作用是:向操作系统请求创建一个新的线程
    • 操作系统创建线程后,会为这个线程分配独立的栈空间和资源。
    • JVM 会在这个新线程的栈中,找到 Thread 对象的 run() 方法,并将其作为新线程的入口点来执行。
  2. run() 方法是什么?

    • run() 方法就是一个普通的 Java 方法。
    • 如果我们直接调用 thread.run(),Java 虚拟机不会做任何特殊处理,它就像调用 anyObject.anyMethod() 一样,只是在当前线程的调用栈中执行这个方法体内的代码。

简单比喻:

想象一下你要去餐厅吃饭。

  • start(): 你走到前台,对服务员说:“点餐,请上菜。” 你说完这句话就可以坐下来玩手机了(main 线程继续执行),厨房(新线程)会开始为你准备食物(执行 run() 任务),你和厨房的工作是并行的。
  • run(): 你直接走进厨房,自己动手洗菜、切菜、炒菜、洗碗,你做完所有这些事之后,才能回到座位上玩手机(main 线程继续执行),你没有创建任何新的“线程”来帮你,所有事情都是你一个人(当前线程)做的。

最佳实践

永远不要直接调用 run() 方法来启动一个线程。

我们的目的是并发执行任务,而不是顺序执行一个任务,如果只是想顺序执行一个任务,直接调用 run() 即可,完全没必要包装成 Thread 对象。

当你想要一个任务在后台并发执行时,请遵循以下步骤:

  1. 创建一个 Runnable 的实现类(或者使用 lambda 表达式)。
  2. 创建一个 Thread 对象,并将 Runnable 实例作为参数传入。
  3. 调用 Thread 对象的 start() 方法。
// 使用 Lambda 表达式(现代 Java 推荐)
Runnable task = () -> {
    System.out.println("任务正在由 " + Thread.currentThread().getName() + " 执行");
};
Thread thread = new Thread(task);
thread.start(); // 正确的启动方式
分享:
扫描分享到社交APP
上一篇
下一篇