JUC并发编程【八股篇】:并发基础

JUC并发编程【八股篇】:并发基础

这是JUC(java.util.concurrent)并发编程系列的基础篇,主要梳理面试高频的“并发八股”核心概念。后续会继续讲锁机制、AQS、线程池、并发工具类、原子类等。内容以原理 + 面试问法 + 背诵要点为主,帮助你快速掌握并发编程的根基。

1. 进程 vs 线程(必考基础)

  • 进程:操作系统资源分配的基本单位。每个进程拥有独立的地址空间、内存、文件句柄等资源。进程之间相互隔离,通信开销大(管道、消息队列、共享内存等)。
  • 线程:操作系统调度(CPU时间片分配)的最小单位。线程是进程内的执行单元,多个线程共享进程的地址空间和资源(堆、方法区、文件描述符等),但每个线程有自己独立的程序计数器、栈、寄存器(防止互相干扰)。

区别总结

  • 进程:资源拥有者,隔离性强,开销大。
  • 线程:调度单位,轻量级,共享资源,开销小,但容易出现线程安全问题。
  • 一个进程至少有一个线程(主线程),Java中线程由JVM管理,但底层映射到OS线程(HotSpot中是1:1模型)。

面试追问

  • Java线程和OS线程的关系?→ Java线程最终依赖OS原生线程实现(pthread或Windows线程)。
  • 为什么多线程能提高效率?→ 充分利用多核CPU、隐藏IO等待(并发 vs 并行)。

2. 并发(Concurrency) vs 并行(Parallelism)

  • 并发:多个任务在同一时间段内交替执行(单核CPU通过时间片轮转实现)。重点是“同时段”。
  • 并行:多个任务在同一时刻真正同时执行(多核CPU)。重点是“同时点”。

举例:

  • 单核CPU跑多个线程 → 并发。
  • 多核CPU每个核跑一个线程 → 并行。

为什么需要并发编程?

  • 充分利用多核CPU性能。
  • 业务拆分,提升响应速度(IO密集型任务特别明显)。
  • 异步处理、提高吞吐量。

并发编程的缺点

  • 线程安全问题(原子性、可见性、有序性被破坏)。
  • 上下文切换开销(频繁切换线程,保存/恢复寄存器、程序计数器等)。
  • 死锁、活锁、饥饿等问题。
  • 调试困难(非确定性)。

3. 并发编程三大特性(核心!)

线程安全必须同时满足以下三点:

  1. 原子性(Atomicity):一个操作要么全部执行成功,要么全部不执行。中间状态对外不可见。
  • 经典破坏:i++(实际是读-改-写三步,非原子)。
  • 保证手段:synchronized、Lock、Atomic原子类、CAS。
  1. 可见性(Visibility):一个线程修改共享变量后,其他线程能立即看到最新值。
  • 破坏原因:CPU缓存、编译器/CPU重排序、JMM工作内存。
  • 保证手段:volatile、synchronized、final、Lock、Happens-Before。
  1. 有序性(Ordering):程序执行的顺序按照代码的先后顺序执行(禁止重排序影响结果)。
  • as-if-serial:单线程看起来像串行执行。
  • 破坏:编译器/JIT/CPU重排序。
  • 保证手段:volatile、synchronized、Happens-Before。

面试常问:并发编程三要素是什么?如何保证?

4. Java内存模型(JMM)—— 并发编程的灵魂

JMM是Java虚拟机规范定义的抽象内存模型,屏蔽了不同硬件/OS的内存差异。

  • 主内存:所有线程共享(对应堆、方法区)。
  • 工作内存(本地内存):每个线程私有的缓存(对应栈 + CPU缓存),线程操作变量时先从主内存拷贝到工作内存。

JMM三大特性

  • 原子性(针对基本操作)。
  • 可见性。
  • 有序性(通过Happens-Before保证)。

重排序:编译器、处理器为了优化性能,可能改变指令执行顺序。但必须遵守as-if-serial(单线程语义不改变)和Happens-Before

5. Happens-Before原则(JMM最核心规则)

如果操作A Happens-Before 操作B,则:

  • A的结果对B可见
  • A的执行顺序在B之前(即使实际可能重排序,但结果必须一致)。

8大Happens-Before规则(背诵重点):

  1. 程序次序规则:同一个线程内,代码按书写顺序执行(前面的Happens-Before后面的)。
  2. 监视器锁规则(synchronized):对同一个锁的unlock操作Happens-Before后续的lock操作。
  3. volatile变量规则:对volatile变量的写操作Happens-Before后续的读操作。
  4. 传递性:如果A HB B,B HB C,则A HB C。
  5. 线程启动规则:Thread.start() Happens-Before 该线程run()中的任何操作。
  6. 线程终止规则:线程中所有操作Happens-Before其他线程检测到该线程终止(join()返回、isAlive()=false)。
  7. 线程中断规则:interrupt()调用Happens-Before被中断线程检测到中断(isInterrupted())。
  8. 对象终结规则:一个对象的构造完成Happens-Before其finalize()开始。

面试追问:volatile为什么能保证可见性和有序性?(依赖volatile写-读的Happens-Before + 内存屏障)

6. 线程的生命周期与状态(5种或6种状态)

Java线程状态(Thread.State枚举):

  1. NEW:新建状态,创建Thread对象但未start()。
  2. RUNNABLE:就绪/运行状态(包含OS的Ready和Running)。start()后进入。
  3. BLOCKED:阻塞状态(等待获取monitor锁,如synchronized未获取)。
  4. WAITING:无限等待(Object.wait()、Thread.join()、LockSupport.park())。
  5. TIMED_WAITING:超时等待(Thread.sleep()、wait(long)、join(long))。
  6. TERMINATED:终止状态(run()执行完或异常退出)。

状态转换图(经典面试题):

  • NEW → start() → RUNNABLE
  • RUNNABLE → 阻塞/等待 → BLOCKED/WAITING/TIMED_WAITING
  • 唤醒后 → RUNNABLE
  • 结束 → TERMINATED

注意:RUNNABLE不等于正在CPU上运行,可能在就绪队列等待时间片。

7. 创建线程的几种方式(及优缺点)

  1. 继承Thread类:重写run()。简单,但不推荐(单继承限制,任务与线程耦合)。
  2. 实现Runnable接口:实现run(),传给Thread。推荐,解耦。
  3. 实现Callable接口 + FutureTask:有返回值,可抛异常。结合线程池使用。
  4. 线程池(推荐):Executors或ThreadPoolExecutor。复用线程,管理线程生命周期,控制并发数。

为什么不推荐直接new Thread?

  • 频繁创建/销毁开销大。
  • 无法统一管理(资源耗尽风险)。
  • 线程池可复用、限流、优雅关闭。

8. 上下文切换(Context Switch)

  • 定义:CPU从一个线程切换到另一个线程时,需要保存当前线程的执行上下文(寄存器、程序计数器、栈指针等),加载目标线程的上下文。
  • 开销:耗费CPU时间(微秒级),频繁切换会降低整体性能。
  • 导致原因:时间片用完、阻塞、yield、优先级等。
  • 优化:减少锁竞争、合理使用线程数(CPU密集型 ≈ 核数,IO密集型 ≈ 核数 * 2~4)、使用协程(虚拟线程,JDK21+)。

9. 死锁(Deadlock)及避免

死锁四个必要条件(Coffman条件):

  1. 互斥条件(资源独占)。
  2. 请求并持有条件(已持有资源,还请求其他)。
  3. 不可剥夺条件(资源只能主动释放)。
  4. 循环等待条件(形成环)。

如何避免

  • 破坏一个条件即可(如按固定顺序申请锁、超时获取锁、资源一次性分配)。
  • 常用:tryLock()带超时、ReentrantLock、避免嵌套锁。

排查:jstack、jconsole、VisualVM查看线程dump,找“waiting for”循环。

10. 其他基础概念

  • 线程安全:多个线程同时访问共享数据时,结果与单线程执行一致。
  • 重入锁:同一个线程可重复获取已持有的锁(synchronized、ReentrantLock都支持)。
  • 守护线程(Daemon):为用户线程服务的线程(GC线程),主线程结束时自动终止。setDaemon(true)必须在start()前调用。
  • ThreadLocal:线程本地变量,每个线程独立副本(后续会详细讲内存泄漏问题)。

并发基础总结口诀(便于背诵):
进程资源分配,线程CPU调度;共享内存有风险,三性必须保;JMM主从内存,HB先行可见;线程五态转,锁争上下文耗;死锁四条件,破坏一即可。

掌握以上内容,你就有了并发编程的坚实根基。下一期我们将深入锁机制(synchronized底层、锁升级、ReentrantLock、AQS原理)。

如果你想看手绘图、具体代码示例、或某部分更详细展开(比如Happens-Before具体例子),随时告诉我!也可以直接问“JUC八股 锁篇”或具体面试题。

文章已创建 5130

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部