start()是JVM启动新线程的指令,会创建新线程并自动调用run();run()只是普通方法,直接调用仍在当前线程同步执行,无并发效果。

零基础学Java线程,关键不是背“有几种方式”,而是弄明白每种写法背后在解决什么问题、用在哪种场景、为什么会有坑。下面从最贴近新手的理解角度讲清楚。
先搞懂一个铁律:start() 和 run() 完全不是一回事
这是90%初学者第一天就踩的坑。你写好一个类,重写了 run() 方法,然后直接调 run() ——它只是普通方法调用,在当前线程里同步执行,根本没创建新线程。真正启动新线程必须调 start(),JVM才会分配栈、注册调度、在新线程中执行 run() 里的逻辑。线上出过真实故障:把 start 写成 run,导致接口卡住几秒,监控全红。
方式一:继承 Thread 类(适合画流程图,不适合写业务)
写法最直白:
- 定义类 extends Thread
- 重写 run() 方法
- new 出对象,调 start()
优点是代码少、容易理解线程和执行体是一体的;但硬伤明显:
立即学习“Java免费学习笔记(深入)”;
- Java 是单继承,你继承了 Thread,就不能再继承其他业务父类了
- 任务逻辑和线程控制绑死,没法复用这个“任务”去跑在线程池里
- run() 没返回值,异常也捕获不到
结论:只建议画示意图、写 Demo 或应付面试时用,别进生产代码。
方式二:实现 Runnable 接口(日常开发主力写法)
把“要做什么”和“谁来执行”分开:
- 写个类 implements Runnable,只管实现 run()
- 把这个类的对象传给 new Thread(runnable)
- 再调 thread.start()
好处很实在:
- 不占继承名额,你的任务类还能 extends 公共基类 或实现其他接口
- 任务本身是个普通对象,以后可以轻松扔进线程池、定时器、甚至单元测试里直接 run()
- 结构清晰,符合“单一职责”——Runnable 只负责定义任务
注意点:run() 还是 void,不能返回结果;抛出的异常会静默吞掉(除非你在 run 里手动 try-catch 打印)。
方式三:Callable + FutureTask(需要结果时才上)
当你写个异步任务,最后得拿个计算结果回来,比如“查三个数据库,汇总总金额”,这时候 Runnable 就不够用了。
- Callable 的 call() 方法能返回值、能抛异常
- 把它包进 FutureTask(它自己实现了 Runnable),就能交给 Thread 或线程池执行
- 用 future.get() 拿结果——但注意:get() 是阻塞的,如果任务卡住,主线程就一起卡住
典型场景:并行调多个 HTTP 接口、批量数据转换后聚合、异步生成报表等。别为了“看起来高级”而滥用,没返回需求就别绕这圈。
顺手提一句:线程池不是“第五种方式”,而是升级方案
前面三种都是在创建线程,而线程创建销毁代价不小。实际项目里几乎不用 new Thread,而是用 ThreadPoolExecutor 或 Executors 工具类。你提交的是任务(Runnable/Callable),线程池内部管理线程的复用、拒绝、回收。等你把上面三种写熟了,下一步自然就是学线程池参数怎么设、队列怎么选、拒绝策略怎么配。


















