当前位置: 首页 > news >正文

JavaSE-12-Java多线程零基础入门核心概念精讲

目录

一、进程与线程:结合SpringBoot实战场景彻底搞懂

1.1 新手必答三大疑问(结合开发日常)

1.2 进程核心概念+通俗理解

1.3 线程核心概念+通俗理解

1.4 进程与线程核心区别

1.5 Java线程底层运行机制+实操演示代码

实操代码:查看当前main方法所属线程

二、线程的创建与启动:三种核心方式+全套实操代码

2.1 方式一:继承Thread类创建线程(代码实操)

核心原理

完整实操演示代码

关键代码注意点(必看)

2.2 方式二:实现Runnable接口创建线程(开发推荐+代码实操)

核心原理&优势

完整实操演示代码

2.3 方式三:实现Callable接口创建线程(为什么要有第三种?深度解释)

很多新手疑问:已经有Thread和Runnable,为什么还要出Callable?

Callable和Runnable核心区别

Callable线程创建实操演示代码

三、线程生命周期五大状态(标准入门版,每个状态独立小标题)

3.1 新建状态(NEW)

3.2 就绪状态(RUNNABLE)

3.3 运行状态(RUNNING)

3.4 阻塞状态(BLOCKED)

3.5 死亡状态(DEAD)

3.6 线程状态流转完整演示代码

四、线程调度:优先级+sleep+yield+join+线程中断

4.1 线程优先级设置

4.2 sleep()线程休眠

4.3 yield()线程让步

4.4 join()线程插队(让别人先走,自己再走)

4.5 interrupt()线程中断(深度详细解释,补齐你要的深入讲解)

1、为什么不用stop()停止线程?

2、interrupt中断核心原理

3、两种中断场景(重点)

4、两个判断方法区别

五、线程查看与监控基础+实操代码

六、守护线程(后台线程)完整详细解释+前台后台对比代码

6.1 守护线程核心概念详细解释

6.2 守护线程使用硬性规则

6.3 守护线程实操演示代码


对于Java后端开发来说,多线程是绕不开的核心地基,不管是普通JavaSE项目、SpringBootWeb业务项目,还是后续线程池、JUC高并发编程、接口压测优化、异步任务处理,全部建立在多线程基础概念之上。

很多新手学多线程学不懂,核心原因就是:只背概念,不懂业务场景对应关系,没有代码实操对照

本篇博客专为Java多线程零基础小白量身打造,全部结合SpringBoot项目、main方法日常开发场景举例答疑,每一个概念不空谈理论,配套专属实操演示代码,看完既能听懂理论,又能直接运行代码看懂效果,彻底吃透多线程入门根基。

一、进程与线程:结合SpringBoot实战场景彻底搞懂

1.1 新手必答三大疑问(结合开发日常)

在正式学概念之前,先解决所有新手最纠结的三个实操问题,对标我们日常开发工作,一看就懂:

疑问1:启动一个SpringBoot服务,是进程还是线程?

答案:启动SpringBoot项目,启动的是一个Java进程。我们平时开发中,点击IDEA启动SpringBoot启动类、服务器上执行java -jar xxx.jar部署项目,操作系统都会分配一块独立专属内存,启动一个独立的JVM虚拟机进程,这个进程就是我们的SpringBoot服务本体,进程之间相互隔离,互不干扰。比如本地同时启动两个不同的SpringBoot项目,就是操作系统里两个独立进程。

疑问2:执行一个Java main方法,是线程还是进程?

答案:执行main方法,先启动一个JVM进程,进程内部默认自带一个主线程执行main代码。简单说:main方法运行,必有一个进程,进程里至少有一个主线程。进程是容器,线程是干活的人。

疑问3:SpringBoot里线程怎么理解?

SpringBoot服务这个进程启动后,默认会创建很多线程:有处理用户浏览器请求的工作线程、有GC垃圾回收后台线程、有定时任务线程。进程是SpringBoot服务整体,线程是服务里每一个独立干活的执行分支,多个线程一起干活,才能同时处理多个用户并发请求。

1.2 进程核心概念+通俗理解

进程是操作系统层面独立运行、内存隔离、互不干扰的完整程序单元,是操作系统资源分配的最小单位。每一个进程都有自己独立的堆内存、方法区内存,进程之间数据不共享,切换开销极大。

日常举例:打开微信是一个进程、打开IDEA是一个进程、启动SpringBoot后端服务是一个进程、运行JavaSE测试main方法也是一个JVM进程。

1.3 线程核心概念+通俗理解

线程是进程内部最小执行单元,也叫轻量级进程,是CPU调度执行的最小单位。一个进程至少包含一个线程(主线程),一个进程可以包含多个线程,同一个进程内所有线程共享进程的堆内存和方法区资源,各自独有自己的方法栈和程序计数器,线程切换开销极小。

形象比喻:进程=一座工厂(SpringBoot整个服务),线程=工厂里的工人,工厂资源共享,每个工人各司其职同时干活,提升整体处理效率。

1.4 进程与线程核心区别

  • 资源隔离不同:进程独立内存互不共享;线程共享进程内存,仅栈内存独立。

  • 开销不同:进程创建、切换开销大;线程开销极小,轻量化。

  • 从属关系:线程依附进程存在,进程销毁,所有线程全部销毁。

1.5 Java线程底层运行机制+实操演示代码

Java中所有代码都是线程执行的,每个线程核心依靠两大组件运行:程序计数器(记录执行位置)、方法调用栈(记录方法执行流程)。我们写的局部变量在栈里,对象实例变量在堆里,多个线程共享堆内存。

实操代码:查看当前main方法所属线程
/** * 1.5 Java线程运行机制演示代码 * 查看main方法运行在哪个线程中 */ public class ThreadRunMechanismDemo { // 实例变量:存堆内存,所有线程共享 private int num = 0; ​ // 普通方法:每个线程调用都会创建独立栈帧 public int addNum(){ // 局部变量:存线程私有栈内存,线程之间互不干扰 int b = 0; num++; b = num; return b; } ​ public static void main(String[] args) { // 获取当前正在执行代码的线程对象 Thread currentThread = Thread.currentThread(); // 打印线程名称:默认main主线程 System.out.println("当前执行main方法的线程名称:" + currentThread.getName()); ​ ThreadRunMechanismDemo demo = new ThreadRunMechanismDemo(); int result = demo.addNum(); System.out.println("执行addNum方法结果:" + result); } } ​

运行结果直观感受:main方法本身就是主线程执行,实例变量堆共享,局部变量栈私有,完美对应线程底层运行机制。

二、线程的创建与启动:三种核心方式+全套实操代码

Java线程本质启动方式只有一种:调用start()向操作系统申请CPU调度;

任务编写方式有三种:继承Thread、实现Runnable、实现Callable。

核心铁律:new线程对象只是新建状态,必须调用start()才是真正多线程启动,直接调用run()只是普通方法串行执行

2.1 方式一:继承Thread类创建线程(代码实操)

核心原理

Thread类是线程本体,继承Thread类,重写run()方法,run()里面写线程要执行的任务逻辑。

完整实操演示代码
/** * 2.1 继承Thread类自定义线程Demo */ // 1.自定义线程类继承Thread public class MyThreadExtend extends Thread { // 重写run方法:线程核心任务代码 @Override public void run() { for (int i = 0; i < 50; i++) { // this.getName():获取当前线程名称 System.out.println(this.getName() + " 正在执行,次数:" + i); try { // 线程休眠100毫秒,交替执行 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } ​ // 测试启动类 class ThreadExtendTest { public static void main(String[] args) { // 2.创建线程对象:新建状态 MyThreadExtend thread1 = new MyThreadExtend(); MyThreadExtend thread2 = new MyThreadExtend(); ​ // 3.调用start()启动线程,开启并发执行 thread1.start(); thread2.start(); ​ // 主线程打印 System.out.println(Thread.currentThread().getName() + " 主线程执行完毕"); } } ​
关键代码注意点(必看)

不要直接调用thread1.run()!直接调用只是普通方法串行执行,不是多线程;只有start()才会向操作系统申请线程资源,并发运行。一个线程只能start()一次,重复调用直接报错IllegalThreadStateException异常。

2.2 方式二:实现Runnable接口创建线程(开发推荐+代码实操)

核心原理&amp;优势

Java类不能多继承,所以开发优先用Runnable接口方式,规避单继承限制,且多个线程可以共享同一个任务对象,适合业务并发处理。

完整实操演示代码
/** * 2.2 实现Runnable接口创建线程Demo(开发推荐) */ // 1.实现Runnable接口,重写run方法 public class MyThreadRunnable implements Runnable { // 共享变量:多个线程共用 private int count = 0; ​ @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " 计数:" + count++); if (count > 10) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } ​ // 测试启动类 class ThreadRunnableTest { public static void main(String[] args) { // 同一个任务对象 MyThreadRunnable runnableTask = new MyThreadRunnable(); // 多个线程共用同一个任务,共享变量count Thread thread1 = new Thread(runnableTask); Thread thread2 = new Thread(runnableTask); ​ // 启动并发执行 thread1.start(); thread2.start(); } } ​

2.3 方式三:实现Callable接口创建线程(为什么要有第三种?深度解释)

很多新手疑问:已经有Thread和Runnable,为什么还要出Callable?

我们前面学的继承Thread类、实现Runnable接口,有一个致命短板:run()方法没有返回值、不能抛出编译时异常

实际开发很多业务场景:线程执行完计算任务、调用第三方接口、批量处理数据后,必须拿到线程执行结果,Runnable做不到,所以Java专门新增Callable接口。

Callable和Runnable核心区别
  • Runnable:无返回值、不能抛编译异常,适合只干活,不需要返回结果

  • Callable:有返回值、可以抛异常,适合线程执行完必须拿结果

注意:Callable不能直接给Thread使用,必须用FutureTask包装一层才能启动线程。

Callable线程创建实操演示代码
/** * 2.3 实现Callable接口创建线程(有返回值专属场景) * 解决Runnable无返回值的痛点 */ import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; ​ // 1.实现Callable接口,泛型指定返回值类型 public class MyThreadCallable implements Callable<Integer> { @Override // call方法:线程任务,有返回值、可抛异常 public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 10; i++) { sum += i; System.out.println(Thread.currentThread().getName() + " 正在累加,当前和:" + sum); Thread.sleep(100); } // 线程执行完毕,返回计算结果 return sum; } } ​ // 测试启动类 class ThreadCallableTest { public static void main(String[] args) throws Exception { // 2.创建Callable任务对象 MyThreadCallable callableTask = new MyThreadCallable(); // 3.用FutureTask包装Callable(中间包装层) FutureTask<Integer> futureTask = new FutureTask<>(callableTask); // 4.传入Thread创建线程对象 Thread thread = new Thread(futureTask); ​ // 启动线程 thread.start(); ​ // 核心:获取线程执行完毕后的返回结果 Integer result = futureTask.get(); System.out.println("线程执行最终累加结果:" + result); } } ​

三、线程生命周期五大状态(标准入门版,每个状态独立小标题)

重点说明:面试和入门学习统一说线程五大状态;JDK源码内部细化为6种状态,新手先学标准5种即可,流转逻辑最清晰。

3.1 新建状态(NEW)

使用new关键字创建线程对象后,线程就处于新建状态。此时仅仅是在堆内存开辟了对象空间,没有创建线程运行栈、程序计数器,没有和操作系统真实线程关联,只是一个普通Java对象,没有任何执行能力。

3.2 就绪状态(RUNNABLE)

调用线程的start()方法后,线程进入就绪状态。JVM为线程分配方法栈、程序计数器,线程进入就绪线程池。这个状态下线程随时等待CPU分配时间片,已经具备运行资格,只差CPU调度执行。

3.3 运行状态(RUNNING)

CPU时间片选中就绪线程,线程正式执行run()方法代码,进入运行状态。单核CPU同一时刻只有一个线程在运行状态,多核CPU可以多个线程同时运行。线程只有从就绪状态才能进入运行状态。

3.4 阻塞状态(BLOCKED)

线程运行过程中,因为sleep休眠、join等待其他线程、争抢同步锁失败、发起IO等待等原因,主动放弃CPU执行权,暂停运行,进入阻塞状态。阻塞期间CPU不会调度该线程,必须解除阻塞后,线程才会重新回到就绪状态排队。

3.5 死亡状态(DEAD)

线程正常执行完run()方法代码,或者运行中抛出异常终止线程,线程进入死亡状态。线程生命周期彻底结束,所有资源释放,线程永久不能再次start()启动。

3.6 线程状态流转完整演示代码

/** * 3.线程五大状态流转演示代码 */ public class ThreadStateDemo extends Thread { @Override public void run() { // 运行状态:执行run方法代码 System.out.println("线程进入运行状态:" + Thread.currentThread().getName()); try { // 执行sleep,进入阻塞状态 System.out.println("线程开始休眠,进入阻塞状态"); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // 阻塞结束,重回就绪→运行,执行完毕进入死亡状态 System.out.println("线程休眠结束,重回运行状态,执行完毕进入死亡状态"); } ​ public static void main(String[] args) { // NEW新建状态 ThreadStateDemo thread = new ThreadStateDemo(); System.out.println("线程创建完毕,当前新建状态"); ​ // 调用start,进入就绪状态 thread.start(); } } ​

四、线程调度:优先级+sleep+yield+join+线程中断

电脑CPU核心有限,多线程并发时,多个就绪线程需要按照规则争抢CPU执行权,这个分配CPU使用权的过程,就是线程调度。Java虚拟机采用抢占式调度模型:优先级高的线程优先抢CPU,优先级相同则随机执行。线程调度是控制线程执行顺序的核心。

4.1 线程优先级设置

Thread类的 setPriority(int) 和getPriority()方法分别用来设置优先级和读取优先级。优先级用整数表示,取值范围是1~10,Thread类有3个静态常量固定优先级。优先级越高,抢到CPU执行机会越多,默认优先级为5。

  • MAX_PRIORITY:取值为 10,表示最高优先级。

  • MIN_PRIORITY:取值为 1,表示最低优先级。

  • NORM_PRIORITY:取值为 5,表示默认优先级。

/** * 4.1 线程优先级演示代码 */ public class ThreadPriorityDemo extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + " 优先级:" + Thread.currentThread().getPriority()); } ​ public static void main(String[] args) { ThreadPriorityDemo t1 = new ThreadPriorityDemo(); ThreadPriorityDemo t2 = new ThreadPriorityDemo(); ​ t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); ​ t1.start(); t2.start(); } } ​

4.2 sleep()线程休眠

当一个线程在运行过程中执行了sleep()方法时,它就会主动放弃CPU执行权,转到阻塞状态。sleep是静态方法,参数指定休眠毫秒数。休眠时间结束后,线程不会直接运行,而是先回到就绪状态,重新排队抢CPU。

sleep休眠期间,不会释放已经持有的锁

// 线程休眠sleep演示:阻塞不释放锁 public class ThreadSleepDemo { public static void main(String[] args) { new Thread(() -> { System.out.println("线程开始执行"); try { // 休眠3秒,进入阻塞状态 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程休眠结束"); }).start(); } } ​

4.3 yield()线程让步

yield()是Thread类静态方法,作用是让当前正在运行的线程主动让出CPU,回到就绪状态,让同优先级或更高优先级的线程先执行。如果没有同优先级线程,yield()无效,当前线程继续运行。

yield()和sleep()最大区别:sleep进入阻塞状态,yield直接回到就绪状态;sleep不管优先级都礼让,yield只礼让同级或高级线程。yield一般只用于测试,开发基本不用。

/** * 4.3 yield线程让步演示代码 */ public class ThreadYieldDemo extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + " 执行:" + i); // 每次执行就让步,让出CPU Thread.yield(); } } ​ public static void main(String[] args) { ThreadYieldDemo t1 = new ThreadYieldDemo(); ThreadYieldDemo t2 = new ThreadYieldDemo(); t1.start(); t2.start(); } }

4.4 join()线程插队(让别人先走,自己再走)

当前线程调用另一个线程的join()方法,当前线程会进入阻塞状态,一直等待对方线程执行完毕,自己才会恢复运行。常用于控制线程执行顺序。

// join插队:让别的线程先执行完,自己再执行 public class ThreadJoinDemo { public static void main(String[] args) throws InterruptedException { Thread joinThread = new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println("插队线程执行:" + i); } }); joinThread.start(); // 主线程等待插队线程执行完毕再往下走 joinThread.join(); System.out.println("主线程最后执行"); } }

4.5 interrupt()线程中断(深度详细解释,补齐你要的深入讲解)

1、为什么不用stop()停止线程?

stop()方法官方早已废弃,强制杀死线程,会直接释放所有锁,导致数据错乱、业务不安全,绝对不推荐使用。

2、interrupt中断核心原理

interrupt()不是直接杀死线程,只是给线程打一个中断标记位=true。线程要不要停止、什么时候停止,由线程自己代码判断、自己优雅退出,安全可控。

3、两种中断场景(重点)
  • 线程运行状态(非阻塞):interrupt()仅设置标记,线程通过isInterrupted()判断标记,自己决定退出。

  • 线程阻塞状态(sleep/wait/join):调用interrupt()会直接抛出InterruptedException异常,并且自动清除中断标记,捕获异常后手动结束线程。

4、两个判断方法区别
  • isInterrupted():判断中断标记,不清除标记,开发常用;

  • interrupted():判断中断标记,判断后自动清空标记,一般不用。

// 优雅中断线程interrupt演示 public class ThreadInterruptDemo extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { // 检测中断标记,优雅退出 if(Thread.currentThread().isInterrupted()){ System.out.println("线程收到中断信号,优雅安全退出"); break; } System.out.println("线程执行:" + i); } } public static void main(String[] args) throws InterruptedException { ThreadInterruptDemo thread = new ThreadInterruptDemo(); thread.start(); Thread.sleep(1000); // 设置中断标记,不强制杀死线程 thread.interrupt(); } }

五、线程查看与监控基础+实操代码

开发调试多线程Bug必备,可快速查看当前运行线程、JVM所有活跃线程,排查线程死锁、线程过多等问题。

import java.util.Map; import java.util.Set; /** * 5.查看线程信息演示代码 */ public class ThreadCheckDemo { public static void main(String[] args) { // 1.获取当前线程引用 Thread mainThread = Thread.currentThread(); System.out.println("当前主线程名称:" + mainThread.getName()); // 2.查看JVM所有活跃线程 Map<Thread, StackTraceElement[]> allThread = Thread.getAllStackTraces(); Set<Thread> threadSet = allThread.keySet(); System.out.println("=====当前JVM所有活跃线程====="); for (Thread thread : threadSet) { System.out.println("线程名称:" + thread.getName() + ",是否守护线程:" + thread.isDaemon()); } } }

六、守护线程(后台线程)完整详细解释+前台后台对比代码

6.1 守护线程核心概念详细解释

守护线程俗称后台线程,专门为前台业务线程提供后台支撑服务。守护线程和前台线程一起运行,但是生命周期完全不一样。

前台线程:我们写的普通业务线程、main主线程都是前台线程,只要有一个前台线程没执行完,JVM就不会退出。

守护线程:后台辅助线程,只要所有前台线程全部执行完毕,不管守护线程任务有没有跑完,JVM都会强制终止守护线程并退出程序。

典型例子:JVM垃圾回收GC线程就是守护线程,专门后台自动回收内存,所有业务线程结束,GC线程自动关闭。

6.2 守护线程使用硬性规则

  • 必须在线程调用start()启动之前,调用setDaemon(true)设置为守护线程;

  • 线程启动后再设置守护线程,直接抛异常;

  • 守护线程适合做日志记录、心跳检测、后台监控,不适合处理核心业务。

6.3 守护线程实操演示代码

/** * 6.守护线程演示代码 */ public class DaemonThreadDemo extends Thread { @Override public void run() { // 守护线程无限循环,理论永久运行 while (true) { System.out.println("守护线程后台持续运行中..."); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { DaemonThreadDemo daemonThread = new DaemonThreadDemo(); // 必须在start之前设置为守护线程 daemonThread.setDaemon(true); daemonThread.start(); // 前台主线程只运行3秒 Thread.sleep(3000); System.out.println("前台主线程执行结束,程序退出,守护线程自动终止"); } }

运行效果:前台main主线程休眠3秒执行完毕退出,哪怕守护线程是死循环,也会直接被JVM终止,完美体现守护线程后台跟随前台线程消亡的核心特点。

http://www.jsqmd.com/news/709448/

相关文章:

  • 高效PR沟通:提升代码协作效率的关键技巧
  • Bedrock Launcher:如何为Minecraft基岩版打造专业级启动体验
  • Elasticsearch搜索算法深度剖析:BM25算法原理及与TF-IDF对比实战指南
  • 山东最推荐高中国际部学校课程有哪些?2026年青岛等地市场选择前五排名 - 十大品牌榜
  • 剑指Offer 48. 最长【不包含】重复字符的子字符串(Medium)/ 1044. 最长重复子串(返回任一子串)(Hard) / 重复子串问题!!!
  • AB 触摸屏常用操作步骤及常见问题解决方案
  • 厦门市翔安区寿苹电脑店:思明电脑置换推荐排行 - LYL仔仔
  • 终极Dell笔记本风扇控制指南:告别噪音困扰的完整解决方案
  • 山东最推荐的中学国际部学校课程有哪些?2026年青岛等地市场选择前五排名 - 十大品牌榜
  • 机房动力环境监控管理系统:全域覆盖,适配多类场景
  • NsCDE Front Panel详解:打造经典工作空间管理器
  • 投资控股集团数智化破局,标杆实践深度解析与转型指南(璞华公开课第6期活动回顾)
  • 告别臃肿!用Hono在Cloudflare Workers上5分钟搭建一个超轻量API(附完整代码)
  • 新手硬件工程师必看:SPI NOR Flash选型与电路设计避坑指南(含W25Q16BV实例)
  • 终极指南:3分钟学会用QtScrcpy在电脑上流畅控制安卓手机
  • React-antd-admin-template权限系统设计:页面权限与路由权限详解
  • 用TensorFlow 2.x和DenseNet121,手把手教你搭建一个数学图形分类器(附完整代码)
  • 本地部署OpenAI TTS:开源项目openai-edge-tts实战指南
  • 2026年乌鲁木齐全屋定制工厂深度横评:本地源头工厂如何破局异地定制困局 - 精选优质企业推荐官
  • 别再只用MD5存密码了!聊聊Java中那些更安全的哈希算法(附SHA-256、bcrypt实战代码)
  • 2026年乌鲁木齐全屋定制工厂购选指南:本地源头工厂如何破解异地定制难题 - 精选优质企业推荐官
  • MCP插件生态搭建全链路拆解,覆盖协议注册、能力协商、上下文同步与热重载调试
  • 给STM32项目加个“不掉电”的时钟:DS1302+纽扣电池完整供电与备份方案
  • pdf2json实战案例:构建企业级PDF数据处理系统
  • Excel/CSV分割工具使用指南
  • 解码回归技术:大语言模型在连续值预测中的应用
  • Element Plus深度解析:如何用现代Vue 3组件库构建企业级应用界面
  • Docker+AI=定时炸弹?资深SRE团队压测27种攻击路径后,锁定6个必须禁用的默认Capabilites
  • 如何快速掌握ASP.NET Core MVC:面向开发者的完整实战指南
  • 气密性测试设备厂家推荐:技术路径与产业选型全景透视 - 品牌评测官