很多 Java 初学者经常犯一个错:
明明创建了线程,却直接调用 run() 方法,线程根本没有“并发”执行。
这到底为什么?这篇文章带你一文看清!
一、start() 和 run() 的本质区别
start() 方法:
-
真正“启动一个新的线程”;
-
由 JVM 调用底层的 native 方法 start0();
-
自动触发该线程的 run() 方法执行。
run() 方法:
-
只是一个普通方法调用;
-
在当前线程内执行,没有线程切换;
-
不具备并发性。
📌 本质上,调用 run() 不会创建新线程,而是同步执行 run() 的逻辑。
二、源码视角:Thread 的start()方法实现
public class Thread implements Runnable {
public synchronized void start() {
if (started)
throw new IllegalThreadStateException();
started = true;
start0(); // native 方法
}
private native void start0();
}
这个 start0() 是一个 native 方法,最终由 JVM 调用操作系统线程创建能力(如 Linux 的 pthread_create())。
而 run() 方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
它就是普通 Java 方法,无任何线程调度逻辑。
三、实战演示:区别有多明显?
public class Demo extends Thread {
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
}
public static void main(String[] args) {
Demo thread = new Demo();
thread.run(); // 同步调用,输出:main
thread.start(); // 异步执行,输出:Thread-0
}
}
📌 thread.run() 实际由主线程执行,没有启动新线程!
四、如果错误地使用 run() 会怎样?
-
会导致阻塞主线程;
-
会出现并发逻辑不生效的问题;
-
如果多个任务都用 run(),会串行执行,完全失去线程意义。
✅ 正确的线程启动方式是:
new Thread(task).start();
而不是:
new Thread(task).run(); // ❌
五、总结:必须牢记的原则
方法 | 是否启动新线程 | 执行线程 | 是否并发 |
---|---|---|---|
start() | ✅ 是 | 新线程 | ✅ 是 |
run() | ❌ 否 | 当前线程(通常是主线程) | ❌ 否 |
六、线程池最佳实践:用Executors或ThreadPoolExecutor管理线程
频繁创建线程(即频繁 new Thread().start())会带来性能损耗,甚至线程爆炸。推荐使用线程池复用线程资源:
✅ 推荐用法(ThreadPoolExecutor更灵活):
ExecutorService pool = new ThreadPoolExecutor(
4, // corePoolSize
10, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), // 工作队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
pool.submit(() -> {
System.out.println("线程名:" + Thread.currentThread().getName());
});
☑️ 你可以根据 CPU 核数、任务特性调整线程池参数,配合监控做到性能与稳定的平衡。
七、Callable + Future:线程返回值的优雅方式
与 Runnable 不同,Callable 支持返回值和异常捕获。配合 Future,可获取执行结果:
示例:
Callable<String> task = () -> {
Thread.sleep(1000);
return "任务完成";
};
ExecutorService pool = Executors.newSingleThreadExecutor();
Future<String> result = pool.submit(task);
System.out.println("结果:" + result.get()); // 阻塞等待结果
📌 Future.get() 会阻塞主线程,建议配合超时控制或 CompletableFuture 进行优化。
八、JVM 层面线程调度与上下文切换的底层理解
Java 线程依赖于 OS 原生线程(如 Linux 的 pthread),JVM 并不直接控制调度,只提供状态管理:
Java线程状态:
-
NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
上下文切换代价:
-
切换寄存器、堆栈、CPU cache,代价高。
-
频繁切换会拖慢系统性能,降低吞吐量。
👉 使用线程池控制并发度,避免创建大量线程,是优化上下文切换的关键。
九、Java 21 虚拟线程:轻量级并发新时代
Java 21 开始,虚拟线程(Virtual Threads)正式成为标准特性,提供了类似 Go 协程的开发体验。
特点:
-
每个任务一个线程,不担心资源消耗
-
JVM 将虚拟线程调度到有限的 OS 线程上执行
-
无需线程池也能高并发
示例(Java 21+):
Runnable task = () -> {
System.out.println("虚拟线程:" + Thread.currentThread());
};
Thread.startVirtualThread(task);
💡适合 I/O 密集型场景,如 HTTP、RPC、大量并发任务处理等。