鞍山市网站建设_网站建设公司_支付系统_seo优化
2026/1/9 12:36:55 网站建设 项目流程

深入 JUC 入门核心:Java 多线程创建与运行机制全解析(Java 实习生必修课)

适用人群

  • 计算机科学与技术、软件工程等专业的在校本科生或研究生,正在学习《操作系统》《并发编程》等课程;
  • Java 初级开发者或实习生,希望系统掌握多线程基础与 JUC(java.util.concurrent)入门知识;
  • 准备 Java 后端岗位面试,需深入理解线程创建方式、生命周期、启动原理及常见误区;
  • 对高并发、异步编程、线程池等高级特性感兴趣的开发者。

本文假设读者已掌握 Java 基础语法(类、接口、Lambda 表达式),并对“进程”“线程”等操作系统概念有初步了解。内容由浅入深,兼顾理论深度与工程实践,适合从“单线程编程”迈向“并发编程”的进阶学习者。


关键词

JUC、Java 并发、多线程、Thread、Runnable、Callable、线程创建、线程启动、start() vs run()、线程生命周期、线程状态、守护线程、线程优先级、Java 实习生、计算机专业核心课、JUC 入门、并发编程基础、操作系统线程模型、JVM 线程映射。


引言:为什么“创建线程”是并发编程的第一道门槛?

在单线程世界里,程序按顺序执行,逻辑清晰、调试简单。但一旦进入多线程领域,“如何正确创建并启动一个线程”就成了无数初学者的“绊脚石”。

你是否曾遇到过以下困惑?

  • 为什么重写run()方法后直接调用它,却没有并发效果?
  • ThreadRunnable到底有什么区别?该用哪一个?
  • 为什么主线程结束,子线程有时还在运行,有时却突然终止?
  • CallableFuture是什么?它们和Runnable有何不同?

这些问题的答案,都藏在Java 线程的创建与运行机制中。作为 JUC(java.util.concurrent)并发包的基石,掌握线程的正确创建方式,是理解后续线程池、锁、原子类、并发集合等高级特性的前提。

本文将从操作系统视角 → JVM 实现 → Java API 层三层递进,全面剖析:

  1. 线程的本质:OS 线程 vs JVM 线程;
  2. 四种线程创建方式:从ThreadCompletableFuture
  3. start()run()的本质区别(附字节码分析);
  4. 线程的生命周期与六种状态
  5. 守护线程、优先级等关键属性
  6. 常见误区与最佳实践

全文超过 9000 字,包含大量代码示例、图解、源码片段与面试高频问题,助你彻底打通多线程入门关!


一、线程的本质:从操作系统到 JVM

1.1 什么是线程?

在操作系统中,线程(Thread)CPU 调度的基本单位,而进程(Process)是资源分配的基本单位。一个进程可包含多个线程,它们共享进程的内存空间(堆、方法区),但拥有独立的栈(Stack)。

📌关键特性

  • 轻量级:创建/切换线程比进程开销小;
  • 共享内存:线程间通信高效,但也带来数据竞争(Race Condition)风险;
  • 并发执行:多核 CPU 可真正并行;单核 CPU 通过时间片轮转实现“伪并行”。

1.2 JVM 如何映射 OS 线程?

Java 线程并非 JVM 自己实现的“用户态线程”,而是直接映射到操作系统的内核线程(Kernel Thread),即1:1 模型

层级实体说明
应用层java.lang.ThreadJava 对象,封装线程行为
JVM 层JNI 调用调用 OS API(如 pthread_create on Linux)
OS 层Kernel Thread真正被 CPU 调度的实体

优势

  • 利用 OS 的调度优化(如多核亲和性);
  • 阻塞操作(如 I/O)不会阻塞整个 JVM。

⚠️代价

  • 每个线程默认占用1MB 栈空间(可通过-Xss调整);
  • 创建过多线程会导致内存溢出(OOM)上下文切换开销剧增

二、四种创建线程的方式:从基础到现代

2.1 方式一:继承 Thread 类(不推荐)

publicclassMyThreadextendsThread{@Overridepublicvoidrun(){System.out.println("Thread running: "+Thread.currentThread().getName());}}// 使用publicclassMain{publicstaticvoidmain(String[]args){MyThreadt=newMyThread();t.start();// 注意:必须调用 start()!}}

优点:简单直观。
缺点

  • Java 不支持多继承,若类已继承其他父类,则无法再继承Thread
  • 线程逻辑任务逻辑耦合在一起,违反“组合优于继承”原则。

反模式:直接调用t.run()—— 这只是普通方法调用,不会启动新线程


2.2 方式二:实现 Runnable 接口(推荐)

publicclassMyTaskimplementsRunnable{@Overridepublicvoidrun(){System.out.println("Task running in: "+Thread.currentThread().getName());}}// 使用publicclassMain{publicstaticvoidmain(String[]args){Threadt=newThread(newMyTask());t.start();// 或使用 Lambda(更简洁)newThread(()->System.out.println("Lambda task")).start();}}

优点

  • 解耦:任务(Runnable)与线程(Thread)分离;
  • 灵活:同一个Runnable实例可被多个线程执行;
  • 符合面向接口编程

最佳实践优先使用Runnable+Thread组合


2.3 方式三:实现 Callable 接口 + FutureTask(获取返回值)

Runnablerun()方法无返回值、不能抛出受检异常。若需返回结果或处理异常,应使用Callable

importjava.util.concurrent.*;publicclassMyCallableimplementsCallable<String>{@OverridepublicStringcall()throwsException{return"Result from "+Thread.currentThread().getName();}}// 使用publicclassMain{publicstaticvoidmain(String[]args)throwsException{Callable<String>task=newMyCallable();FutureTask<String>futureTask=newFutureTask<>(task);Threadt=newThread(futureTask);t.start();// 获取结果(会阻塞直到任务完成)Stringresult=futureTask.get();System.out.println(result);}}

关键组件

  • Callable<V>:类似Runnable,但call()可返回值、抛异常;
  • FutureTask<V>:包装Callable,实现Runnable接口,可被Thread执行;
  • Future<V>:提供get()isDone()cancel()等方法管理异步任务。

💡注意futureTask.get()阻塞调用,若任务未完成,主线程会一直等待。


2.4 方式四:使用线程池(ExecutorService)——生产首选

手动创建Thread存在资源浪费、缺乏管理、易 OOM等问题。JUC 提供线程池抽象,统一管理线程生命周期。

importjava.util.concurrent.*;publicclassThreadPoolExample{publicstaticvoidmain(String[]args){// 创建固定大小线程池ExecutorServiceexecutor=Executors.newFixedThreadPool(2);// 提交 Runnable 任务executor.submit(()->System.out.println("Runnable task"));// 提交 Callable 任务Future<String>future=executor.submit(()->"Callable result");try{System.out.println(future.get());}catch(Exceptione){e.printStackTrace();}// 关闭线程池executor.shutdown();}}

优势

  • 复用线程:避免频繁创建/销毁开销;
  • 控制并发数:防止系统过载;
  • 任务队列:缓冲突发请求;
  • 统一管理:优雅关闭、监控等。

生产环境准则永远不要直接 new Thread(),而应使用线程池


三、start()run()的本质区别:字节码级剖析

这是面试高频题,也是初学者最易混淆的概念。

3.1 表象差异

Threadt=newThread(()->System.out.println("Hello"));t.run();// 输出 "Hello",但仍在主线程执行t.start();// 输出 "Hello",在新线程执行

3.2 源码揭秘

查看Thread.java(JDK 8):

// run() 方法:只是一个普通方法@Overridepublicvoidrun(){if(target!=null){target.run();// target 即传入的 Runnable}}// start() 方法:触发 JVM 创建新线程publicsynchronizedvoidstart(){if(threadStatus!=0)thrownewIllegalThreadStateException();// 添加到线程组group.add(this);booleanstarted=false;try{start0();// native 方法!started=true;}finally{if(!started)group.threadStartFailed(this);}}// native 方法,由 JVM 实现privatenativevoidstart0();

3.3 字节码与执行流程

  • 调用run()

    • JVM 直接在当前线程栈帧中执行run()方法;
    • 无新线程创建,等同于普通方法调用。
  • 调用start()

    1. JVM 通过 JNI 调用 OS API(如pthread_create)创建内核线程;
    2. 新线程启动后,自动回调run()方法;
    3. 此时run()新线程的栈中执行。

📊流程图

主线程 │ ├─ t.run() → 直接执行 run()(主线程栈) │ └─ t.start() ↓ [JVM] → [OS] 创建新线程 ↓ 新线程 → 自动调用 t.run()(新线程栈)

3.4 常见错误:重复调用 start()

Threadt=newThread(()->{});t.start();t.start();// 抛出 IllegalThreadStateException!

原因start()方法开头检查threadStatus != 0。线程一旦启动,状态变为非 0,再次调用即非法。

线程不可复用:一个Thread对象只能启动一次。


四、线程的生命周期与六种状态(Thread.State)

Java 线程并非只有“运行”和“停止”两种状态,而是有六种明确的状态(自 JDK 5 起定义于Thread.State枚举)。

状态说明触发条件
NEW新建new Thread()后,未调用start()
RUNNABLE可运行已调用start(),等待 CPU 调度(含正在运行)
BLOCKED阻塞等待获取 synchronized 锁
WAITING等待调用wait()join()LockSupport.park()
TIMED_WAITING超时等待调用sleep(time)wait(time)join(time)
TERMINATED终止run()方法执行完毕或异常退出

4.1 状态转换图

+--------+ | NEW | +---+----+ | start() v +------+-------+ | RUNNABLE |<------------------+ +------+-------+ | | | +-------v-------+ +-------------v-------------+ | BLOCKED | | WAITING / | +-------+-------+ | TIMED_WAITING | | +-------------+-------------+ | | acquire lock notify()/interrupt()/timeout | | +------------->-------------+ | run() ends v +-------+--------+ | TERMINATED | +----------------+

4.2 代码演示状态变化

publicclassThreadStateDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt=newThread(()->{try{Thread.sleep(2000);// TIMED_WAITING}catch(InterruptedExceptione){e.printStackTrace();}});System.out.println(t.getState());// NEWt.start();System.out.println(t.getState());// RUNNABLE(可能短暂)Thread.sleep(100);System.out.println(t.getState());// TIMED_WAITINGt.join();// 主线程 WAITING,直到 t 结束System.out.println(t.getState());// TERMINATED}}

🔍注意RUNNABLE包含就绪(Ready)运行(Running)两种 OS 状态,JVM 不区分。


五、线程的关键属性:守护线程与优先级

5.1 守护线程(Daemon Thread)

定义:为其他线程提供服务的线程。当所有非守护线程结束时,JVM 会强制终止所有守护线程并退出。

典型应用

  • GC 线程;
  • 心跳检测线程;
  • 日志 flush 线程。

设置方式

Threadt=newThread(()->{while(true){System.out.println("Daemon running...");try{Thread.sleep(1000);}catch(InterruptedExceptione){}}});t.setDaemon(true);// 必须在 start() 前设置!t.start();// 主线程结束 → JVM 退出,守护线程被强制终止System.out.println("Main thread ends.");

⚠️重要规则

  • setDaemon(true)必须在start()之前调用,否则抛IllegalThreadStateException
  • 守护线程中不应执行关键业务逻辑(可能被突然终止)。

5.2 线程优先级(Priority)

Java 提供 1~10 的优先级(Thread.MIN_PRIORITY=1,MAX_PRIORITY=10,NORM_PRIORITY=5),但仅作为建议,实际调度由 OS 决定。

Threadt1=newThread(()->{/* 高优先级任务 */});Threadt2=newThread(()->{/* 低优先级任务 */});t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);t1.start();t2.start();

⚠️现实情况

  • Linux 默认忽略 Java 优先级;
  • Windows 有一定效果,但不保证;
  • 不要依赖优先级控制执行顺序!应使用同步机制(如CountDownLatch)。

六、常见误区与最佳实践

误区一:“调用 run() 就是启动线程”

纠正run()只是普通方法调用,不会创建新线程。必须调用start()

误区二:“线程可以复用”

纠正:一个Thread对象只能启动一次。若需多次执行任务,应:

  • 使用Runnable+ 新Thread
  • 或使用线程池。

误区三:“守护线程可以做重要工作”

纠正:守护线程会在 JVM 退出时被强制终止,不能用于写文件、发消息等关键操作

误区四:“高优先级线程一定先执行”

纠正:优先级只是 hint,不能保证执行顺序。并发控制应依赖锁、信号量等同步工具。


七、JUC 入门:从 Thread 到 ExecutorService

虽然Thread是基础,但 JUC 的核心思想是“解耦任务提交与执行”。因此,线程池(ExecutorService)才是现代 Java 并发的标准入口

7.1 Executors 工厂方法(谨慎使用!)

方法说明风险
newFixedThreadPool(n)固定大小线程池任务队列无界,可能 OOM
newCachedThreadPool()缓存线程池(60s 超时)线程数无界,可能 OOM
newSingleThreadExecutor()单线程池任务队列无界
newScheduledThreadPool(n)支持定时任务同上

⚠️阿里《Java 开发手册》禁止使用 Executors
原因:默认使用LinkedBlockingQueue(无界队列),高负载下易导致内存溢出。

7.2 正确创建线程池(推荐)

ExecutorServiceexecutor=newThreadPoolExecutor(2,// corePoolSize4,// maximumPoolSize60L,TimeUnit.SECONDS,// keepAliveTimenewArrayBlockingQueue<>(100),// 有界队列newThreadPoolExecutor.CallerRunsPolicy()// 拒绝策略);

参数含义

  • corePoolSize:核心线程数(即使空闲也不回收);
  • maximumPoolSize:最大线程数;
  • workQueue:任务队列(必须有界!);
  • RejectedExecutionHandler:拒绝策略(如 CallerRunsPolicy:由提交者线程执行)。

八、学习建议与扩展阅读

8.1 动手实验清单

  1. 验证 start() vs run():打印线程 ID,观察是否相同;
  2. 模拟守护线程:主线程 sleep 后退出,观察守护线程行为;
  3. 线程状态监控:用jstack <pid>查看线程状态;
  4. 线程池对比:分别用new Thread()ThreadPoolExecutor执行 1000 个任务,观察资源消耗。

8.2 推荐资料

  • 📘《Java 并发编程实战》(Brian Goetz)
    并发领域的圣经,第 5 章详解线程基础。
  • 📘《深入理解 Java 虚拟机》— 周志明
    第 12 章“Java 内存模型与线程”。
  • 📄Oracle Concurrency Tutorial
    官方并发教程。
  • 🎥Bilibili 视频
    • 尚硅谷《JUC 并发编程》
    • 黑马程序员《Java 多线程核心技术》

8.3 面试高频问题

  • 创建线程有哪几种方式?各有什么优缺点?
  • start()run()的区别是什么?
  • 线程有哪些状态?如何转换?
  • 什么是守护线程?应用场景?
  • 为什么不要直接使用 Executors 创建线程池?

九、总结

“创建线程”看似简单,实则蕴含操作系统、JVM、Java API 三层知识。本文系统讲解了:

  • 线程的本质:JVM 如何映射 OS 线程;
  • 四种创建方式:从Thread到线程池的演进;
  • start()vsrun():字节码级原理剖析;
  • 六种线程状态:生命周期完整图谱;
  • 守护线程与优先级:关键属性与使用场景;
  • JUC 入门指引:为何线程池是生产首选。

最后寄语
并发编程的旅程,始于正确创建一个线程。
不要满足于“能跑起来”,
而要追问“为什么这样设计”。
从今天起,用jstack观察你的线程,
用线程池管理你的任务,
你将真正踏入高并发的世界。


欢迎在评论区交流!
👉 你在实习中是否遇到过线程相关的问题?
👉 对哪种线程创建方式最感兴趣?

点赞 + 收藏 + 关注,获取更多 JUC 与并发编程干货!🚀

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询