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

【JavaSE全面教学】Java多线程与并发基础Day15(2026年)

写在前面:这是JavaSE系列的最后一篇!多线程是Java进阶的必经之路,也是面试中区分初级和中高级开发者的分水岭。今天我们讲多线程的基础知识,包括线程的创建、生命周期、同步机制和线程池。

文章目录

    • 一、进程与线程
      • 1.1 基本概念
      • 1.2 为什么需要多线程?
    • 二、创建线程的四种方式
      • 2.1 继承Thread类
      • 2.2 实现Runnable接口(推荐)
      • 2.3 实现Callable接口(有返回值)
      • 2.4 线程池(生产环境推荐)
      • 2.5 四种方式对比
    • 三、线程的生命周期
      • 3.1 六种状态
      • 3.2 状态转换示例
    • 四、线程同步
      • 4.1 为什么需要同步?
      • 4.2 synchronized关键字
      • 4.3 Lock接口(更灵活)
      • 4.4 volatile关键字
    • 五、线程通信
      • 5.1 wait和notify
      • 5.2 sleep和wait的区别
    • 六、线程池
      • 6.1 为什么需要线程池?
      • 6.2 创建线程池
      • 6.3 ThreadPoolExecutor(推荐)
      • 6.4 拒绝策略
    • 参考资料
    • 七、面试高频考点
      • 考点1:start和run的区别
      • 考点2:synchronized和Lock的区别
      • 考点3:线程池的核心参数
    • 八、总结
    • 🎉 JavaSE全面教学系列完结

一、进程与线程

1.1 基本概念

进程(Process): - 操作系统分配资源的基本单位 - 每个进程有独立的内存空间 - 例如:打开一个浏览器就是一个进程 线程(Thread): - CPU调度的基本单位 - 一个进程可以包含多个线程 - 线程共享进程的内存空间 - 例如:浏览器中同时下载多个文件,每个下载任务是一个线程

1.2 为什么需要多线程?

实际场景:想象一下你在浏览器里同时下载多个文件。如果是单线程,必须等第一个文件下载完才能开始第二个。而多线程可以让多个下载任务同时进行,大大缩短总时间。

// 单线程:任务串行执行// 任务1(3秒)→ 任务2(2秒)→ 任务3(5秒)= 总共10秒// 多线程:任务并行执行// 任务1(3秒)// 任务2(2秒) → 总共5秒(取决于最慢的任务)// 任务3(5秒)// 好处:// 1. 提高CPU利用率// 2. 提高程序响应速度// 3. 充分利用多核CPU

经验之谈:在实际项目中,多线程最常见的应用场景是:批量数据处理、异步任务执行、定时任务调度。但也要注意,线程不是越多越好,过多的线程会导致上下文切换开销增大,反而降低性能。


二、创建线程的四种方式

2.1 继承Thread类

// 方式1:继承Thread类,重写run方法classMyThreadextendsThread{@Overridepublicvoidrun(){for(inti=0;i<5;i++){System.out.println("子线程:"+i);}}}// 启动线程publicclassTest{publicstaticvoidmain(String[]args){MyThreadt=newMyThread();t.start();// 启动线程(不是run!)for(inti=0;i<5;i++){System.out.println("主线程:"+i);}}}// 注意:调用start()才是启动线程// 调用run()只是普通方法调用,还是在主线程执行

踩坑提醒:新手最容易犯的错误就是调用run()而不是start()。调用run()不会创建新线程,只是在当前线程执行方法体,完全达不到多线程的效果。

2.2 实现Runnable接口(推荐)

// 方式2:实现Runnable接口classMyRunnableimplementsRunnable{@Overridepublicvoidrun(){for(inti=0;i<5;i++){System.out.println("子线程:"+i);}}}// 启动线程publicclassTest{publicstaticvoidmain(String[]args){Threadt=newThread(newMyRunnable());t.start();}}// 方式3:Lambda表达式(Java 8+,最简洁)newThread(()->{System.out.println("子线程运行");}).start();

2.3 实现Callable接口(有返回值)

importjava.util.concurrent.*;// 方式4:实现Callable接口(可以返回结果)classMyCallableimplementsCallable<Integer>{@OverridepublicIntegercall()throwsException{intsum=0;for(inti=1;i<=100;i++){sum+=i;}returnsum;// 返回结果}}publicclassTest{publicstaticvoidmain(String[]args)throwsException{// 创建FutureTaskFutureTask<Integer>task=newFutureTask<>(newMyCallable());// 启动线程newThread(task).start();// 获取结果(会阻塞直到线程执行完毕)Integerresult=task.get();System.out.println("1到100的和:"+result);// 5050}}

2.4 线程池(生产环境推荐)

经验之谈:在实际项目中,千万不要用new Thread()创建线程!线程创建和销毁是有开销的,而且线程数不可控可能导致OOM。线程池可以复用线程、控制并发数,是生产环境的标准做法。

importjava.util.concurrent.*;// 创建线程池ExecutorServicepool=Executors.newFixedThreadPool(3);// 3个线程的固定线程池// 提交任务pool.execute(()->System.out.println("任务1"));pool.execute(()->System.out.println("任务2"));pool.execute(()->System.out.println("任务3"));// 关闭线程池pool.shutdown();

2.5 四种方式对比

方式优点缺点推荐度
继承Thread简单直接Java单继承限制★★☆☆☆
实现Runnable灵活,可继承其他类无返回值★★★★☆
实现Callable有返回值稍复杂★★★★☆
线程池复用线程,性能好需要理解线程池原理★★★★★

三、线程的生命周期

3.1 六种状态

NEW(新建) ↓ start() RUNNABLE(可运行) ↓ 获得CPU时间片 ↓ 等待锁 / sleep / join / IO BLOCKED(阻塞) ← 获得锁 → RUNNABLE WAITING(等待) ← notify / notifyAll → RUNNABLE TIMED_WAITING(超时等待) ← 超时结束 → RUNNABLE ↓ run()执行完毕 TERMINATED(终止)

3.2 状态转换示例

Threadt=newThread(()->{try{Thread.sleep(1000);// TIMED_WAITING}catch(InterruptedExceptione){e.printStackTrace();}});// t的状态:NEWt.start();// t的状态:RUNNABLE// t执行完毕后// t的状态:TERMINATED

四、线程同步

4.1 为什么需要同步?

踩坑提醒:多线程操作共享数据时,不加同步机制会导致数据不一致。我曾经遇到过一个Bug:多个线程同时修改库存数量,结果出现超卖。这就是典型的竞态条件问题。

// 不加同步的例子:多个线程同时操作共享数据classCounter{privateintcount=0;publicvoidincrement(){count++;// 不是原子操作!}}// count++ 实际上是3步操作:// 1. 读取count的值// 2. count + 1// 3. 写回count// 如果两个线程同时执行count++,可能出现:// 线程A读取count=0 → 线程B读取count=0// 线程A写入count=1 → 线程B写入count=1// 结果应该是2,实际是1(数据不一致)

4.2 synchronized关键字

// 方式1:同步代码块classCounter{privateintcount=0;privatefinalObjectlock=newObject();publicvoidincrement(){synchronized(lock){// 锁住对象count++;}}}// 方式2:同步方法classCounter{privateintcount=0;publicsynchronizedvoidincrement(){// 锁住thiscount++;}}// 方式3:同步静态方法classCounter{privatestaticintcount=0;publicstaticsynchronizedvoidincrement(){// 锁住Class对象count++;}}

4.3 Lock接口(更灵活)

经验之谈:Lock比synchronized更灵活,可以实现公平锁、可中断锁、超时获取锁等。但使用Lock时一定要在finally中释放锁,否则一旦出现异常,锁就永远释放了,其他线程会一直阻塞。

importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;classCounter{privateintcount=0;privatefinalLocklock=newReentrantLock();publicvoidincrement(){lock.lock();// 加锁try{count++;}finally{lock.unlock();// 一定要在finally中释放锁}}}

踩坑提醒:忘记在finally中unlock是Lock使用的常见错误。相比之下synchronized会自动释放锁,更安全一些。

4.4 volatile关键字

// volatile:保证可见性,不保证原子性classVolatileDemo{privatevolatilebooleanrunning=true;publicvoidstop(){running=false;// 一个线程修改}publicvoidrun(){while(running){// 另一个线程能立即看到变化// 执行任务}}}// volatile vs synchronized// volatile:保证可见性,不保证原子性// synchronized:保证可见性和原子性

五、线程通信

5.1 wait和notify

classSharedResource{privateintcount=0;// 生产者publicsynchronizedvoidproduce()throwsInterruptedException{while(count>=10){wait();// 等待(释放锁)}count++;System.out.println("生产:"+count);notifyAll();// 通知消费者}// 消费者publicsynchronizedvoidconsume()throwsInterruptedException{while(count<=0){wait();// 等待}count--;System.out.println("消费:"+count);notifyAll();// 通知生产者}}

5.2 sleep和wait的区别

特性sleepwait
所属类ThreadObject
释放锁不释放释放
使用位置任何地方synchronized块中
唤醒方式自动超时notify/notifyAll

六、线程池

6.1 为什么需要线程池?

// 不用线程池:// 每次执行任务都创建新线程// 创建和销毁线程开销大// 线程数量不可控,可能OOM// 用线程池:// 复用线程,减少创建销毁开销// 控制最大并发数// 提供任务队列和拒绝策略

6.2 创建线程池

importjava.util.concurrent.*;// 1. 固定大小线程池ExecutorServicepool1=Executors.newFixedThreadPool(5);// 5个线程,任务队列无界// 2. 缓存线程池ExecutorServicepool2=Executors.newCachedThreadPool();// 线程数量不固定,按需创建// 3. 单线程池ExecutorServicepool3=Executors.newSingleThreadExecutor();// 只有1个线程,保证任务按顺序执行// 4. 定时任务线程池ScheduledExecutorServicepool4=Executors.newScheduledThreadPool(3);pool4.schedule(()->System.out.println("3秒后执行"),3,TimeUnit.SECONDS);pool4.scheduleAtFixedRate(()->System.out.println("定时执行"),0,1,TimeUnit.SECONDS);

6.3 ThreadPoolExecutor(推荐)

踩坑提醒:阿里巴巴Java开发手册明确规定,生产环境禁止使用Executors的快捷方法创建线程池!因为newFixedThreadPool和newSingleThreadExecutor使用的是无界队列,任务堆积会导致OOM;newCachedThreadPool允许无限创建线程,也会导致OOM。

// 生产环境推荐手动创建线程池(不用Executors工厂方法)ThreadPoolExecutorpool=newThreadPoolExecutor(2,// 核心线程数5,// 最大线程数60L,// 空闲线程存活时间TimeUnit.SECONDS,// 时间单位newArrayBlockingQueue<>(100),// 任务队列(有界队列)Executors.defaultThreadFactory(),// 线程工厂newThreadPoolExecutor.CallerRunsPolicy()// 拒绝策略);// 提交任务pool.execute(()->System.out.println("任务1"));// 无返回值Future<Integer>future=pool.submit(()->{// 有返回值return42;});Integerresult=future.get();// 获取结果// 关闭线程池pool.shutdown();// 不接受新任务,等待已提交的任务完成

6.4 拒绝策略

策略说明
AbortPolicy抛出RejectedExecutionException(默认)
CallerRunsPolicy由提交任务的线程执行
DiscardPolicy直接丢弃任务
DiscardOldestPolicy丢弃队列中最老的任务

参考资料

  1. Oracle官方文档 - Concurrency
  2. Baeldung - Java Concurrency Tutorial

七、面试高频考点

考点1:start和run的区别

// start():启动新线程,在子线程中执行run()// run():普通方法调用,在当前线程中执行

追问:多次调用start会怎样?
答案:会抛出IllegalThreadStateException。线程一旦启动,就不能再次启动。

考点2:synchronized和Lock的区别

特性synchronizedLock
锁释放自动释放手动unlock
中断不可中断可以中断
公平性非公平可选公平
条件绑定一个锁一个条件一个锁多个条件

延伸:什么情况下用Lock不用synchronized?
答案:需要公平锁、可中断锁、超时获取锁、多个条件变量时,用Lock更灵活。

考点3:线程池的核心参数

// corePoolSize:核心线程数// maximumPoolSize:最大线程数// keepAliveTime:空闲线程存活时间// workQueue:任务队列// handler:拒绝策略

追问:线程池的执行流程是怎样的?
答案:提交任务→核心线程是否已满?否:创建核心线程执行任务;是:加入队列;队列已满?否:加入队列;是:创建非核心线程;非核心线程数达上限?否:创建非核心线程;是:执行拒绝策略。


八、总结

今天我们学习了:

  • ✅ 进程和线程的概念
  • ✅ 创建线程的四种方式
  • ✅ 线程的生命周期
  • ✅ 线程同步(synchronized、Lock、volatile)
  • ✅ 线程通信(wait/notify)
  • ✅ 线程池的使用

重点记忆

  1. 推荐用Runnable或线程池创建线程
  2. start()启动线程,run()只是普通调用
  3. synchronized保证原子性和可见性
  4. volatile只保证可见性
  5. 生产环境用手动创建的ThreadPoolExecutor

🎉 JavaSE全面教学系列完结

恭喜你学完了JavaSE全面教学系列的全部15篇文章!

学习路线回顾

Week 1(Day1-5):基础语法 ✅ Week 2(Day6-10):面向对象 ✅ Week 3(Day11-15):进阶特性 ✅

下一步建议

  1. 复习本系列所有文章
  2. 把每篇的代码都自己敲一遍
  3. 刷LeetCode巩固语法
  4. 学习JavaWeb/框架(Spring Boot等)
  5. 做一个完整的项目

互动话题:恭喜你完成了JavaSE全面教学系列!你学完之后有什么收获或困惑?欢迎在评论区分享!

如果这个系列对你有帮助,欢迎点赞、收藏、关注三连支持!后续我会更新Java进阶系列,关注我不迷路👇


本文为【JavaSE全面教学】系列第15篇(完结),感谢你的学习!

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

相关文章:

  • i.MX6ULL LCD驱动适配实战:从设备树到时序调试全解析
  • ISTA 2B-2011 (2022) 全解析|>68kg 重型包装部分模拟运输测试标准
  • 技术从业者的副业指南:如何利用技术技能赚钱
  • ARM核心板存储选型实战:从DDR到eMMC的避坑指南
  • AI写作辅助平台8款一键生成论文工具势力榜,毕业护航利器!
  • Windows安卓子系统终极指南:三步免费安装与完整使用教程
  • 【Echarts实战】告别拥挤!5种策略动态调整X轴刻度间距,让长文本清晰呈现
  • 如何在Windows电脑上轻松安装APK文件:APK安装器终极指南
  • 7个DLL依赖问题调试技巧:Dependencies工具实战指南
  • 2026年抖音视频解析在线提取工具实测对比,吹上天的热门款不敌黑马差距竟然这么大
  • 国内高校学生常用的AI论文工具有哪些?
  • 【Midjourney摄影级出图秘籍】:5大核心相机参数(--ar、--s、--q、--style、--v)的黄金配比与失效避坑指南
  • ARM弱内存序模型解析:多核并发编程中的内存屏障与同步原语
  • 为Claude Code配置Taotoken作为备用模型服务商
  • 在深圳及珠三角地区寻找模胚(模架)机加工厂家的思路 - 昌晖模胚
  • 数字电路实战:从奇偶校验到数值比较的可靠设计
  • OpenWrt开发板IP地址设置指南:从网络拓扑到配置实战
  • JavaBean ---封装类
  • 3步打造智能设计转换桥梁:从Figma到Unity的无缝对接方案
  • NVIDIA Vera CPU:首款专为Agentic AI设计的CPU架构深度解析
  • 如何一键安装所有Visual C++运行库:解决DLL缺失错误的终极方案
  • 2026年文章去AI痕迹大挑战,言笔AI高效降AI率必备之选 - 降AI实验室
  • RT-Thread SPARK CAN通信内核:从分层架构到多任务并发处理的深度解析
  • 技术从业者的理财攻略:如何实现财务自由
  • 保姆级教程:用CANoe CAPL脚本复现一次完整的ECU刷写(附Trace分析)
  • 告别connect!用Qt Creator的UI设计器自动生成信号槽连接(附实战案例)
  • RTOS如何通过确定性调度与内存管理增强嵌入式系统安全可靠性
  • AI写教材必备:低查重AI工具,快速生成符合要求的教材内容!
  • 2026年郑州婚纱摄影宝藏店铺,闭眼可冲 - 品牌企业推荐师(官方)
  • 水贝黄金购买渠道有哪些? - 品牌企业推荐师(官方)