调用 start() 才真正启动新线程并由 jvm 分配独立栈帧,而直接调用 run() 仅是主线程执行普通方法,复用当前栈帧、无并发效果;局部变量表线程私有,天然支持线程安全。

调用 start() 会真正启动新线程,JVM 为其分配独立栈帧;而直接调用 run() 只是普通方法执行,复用当前线程的栈帧,不产生新线程上下文。
新线程启动时 JVM 如何分配栈帧
当调用 Thread.start(),JVM 会为该线程创建全新的虚拟机栈,并在其中压入第一个栈帧——对应 run() 方法的执行环境:
- 每个线程拥有私有的 JVM 栈,生命周期与线程一致;栈由多个栈帧组成,每帧对应一次方法调用
-
run()方法被调用时,JVM 按编译期确定的大小,在新栈中分配局部变量表、操作数栈等结构 - 局部变量表首槽(索引 0)存的是当前
Thread实例引用(即this),后续槽位按参数和局部变量声明顺序填充 - 所有局部变量(如
int k = 100;)都直接存于该栈帧的局部变量表中,生命周期绑定此帧
直接调用 run() 不触发栈帧隔离
若跳过 start(),手动写 thread.run(),本质是主线程调用一个普通实例方法:
- 不会创建新线程,也不会分配新栈;
run()的栈帧压入当前线程(比如 main 线程)的栈中 - 局部变量(如示例中的
int k = 100;)仍存于主线程栈帧里,与其他局部变量共享同一栈空间 - 没有并发执行效果,
run()内代码仍是串行执行,无法体现多线程语义
局部变量表如何支持线程安全
栈帧的局部变量表天然具备线程隔离性,这是 Java 多线程安全的底层基础之一:
- 局部变量表位于线程私有栈中,其他线程无法读写,无需同步即可保证可见性与独占性
- 即使多个线程同时执行同一个
run()方法,每个线程都有自己的局部变量表副本,互不影响 - 64 位类型(
long、double)占用两个连续变量槽,但仍是原子分配,不会出现“半个变量”跨线程问题 - 变量槽可复用(超出作用域后被覆盖),但复用仅限本栈帧内,不跨线程、不跨方法调用
从字节码看两者的调用差异
编译后,start() 和 run() 在字节码层面完全无关:
-
start()调用最终进入本地方法(JVM_StartThread),由 JVM C++ 层创建 OS 线程并初始化新栈 -
run()是一个普通虚方法调用指令(invokevirtual),和调用toString()无本质区别 - 字节码中看不到“新栈帧分配”指令——那是 JVM 运行时行为,由线程调度与内存管理子系统完成











