大家好,我是程序员二叉。


简介

本文整理后端面试必考 Java 并发基础全集:进程与线程区别、Go协程与Java线程差异、线程三种创建方式优缺点、线程五大生命周期、守护线程机制、四大线程方法区别、wait/notify加锁原理、线程上下文切换原理,知识点全覆盖,可直接用于面试背诵。欢迎点赞关注收藏。


一、进程和线程的核心区别

1. 核心定义

  • 进程:操作系统资源分配的最小单位
  • 线程:CPU 调度执行的最小单位

2. 详细区别

  1. 资源隔离
  • 进程:独立内存、资源完全隔离
  • 线程:共享进程资源,仅私有栈、程序计数器
  1. 开销大小
  • 进程:创建、切换、销毁 开销极大
  • 线程:轻量级,开销极小
  1. 通信难度
  • 进程:IPC通信(管道、共享内存、消息队列)复杂
  • 线程:直接共享变量,通信简单
  1. 容错性
  • 进程:相互独立,一个崩不影响其他
  • 线程:一挂全挂,一个线程异常终止,整个进程退出

3. 一句话总结

进程是资源容器,线程是真正干活的执行单元。


二、Go协程(Goroutine)与Java线程区别

  1. 映射模型
  • Java线程:1:1 内核线程,操作系统调度
  • Go协程:M:N 用户态线程,Go runtime 调度
  1. 内存占用
  • Java线程:默认栈 1MB
  • Go协程:初始栈 2KB,动态伸缩
  1. 并发量级
  • Java:上限几千线程
  • Go:轻松 百万级协程
  1. 切换开销
  • Java线程:内核态切换,开销大
  • Go协程:用户态切换,无系统调用,极快

总结

Java线程是重量级系统线程,Go协程是超轻量级用户态执行单元。


三、Java线程三种创建方式 + 优缺点

1. 继承 Thread 类

  • 优点:写法简单直观
  • 缺点:Java单继承限制,无法继承其他类,扩展性差

2. 实现 Runnable 接口(推荐)

  • 优点:规避单继承、适合资源共享、解耦
  • 缺点:无返回值、不能抛异常

3. Callable + FutureTask

  • 优点:有返回值、可捕获异常
  • 缺点:代码相对繁琐

注意:调用 start() 才是开启新线程,run() 只是普通方法调用。


四、线程五大生命周期 & 流转

五大状态

  1. New 新建:创建线程对象,未启动
  2. Runnable 就绪:调用 start(),等待CPU时间片
  3. Running 运行:抢到CPU时间片,正在执行
  4. Blocked 阻塞:锁阻塞、等待唤醒、sleep、join
  5. Terminated 终止:线程执行结束/异常退出

状态流转

  • New → start() → Runnable
  • Runnable ↔ Running(CPU调度切换)
  • Running → Blocked(sleep/join/wait/抢锁失败)
  • Blocked → Runnable(唤醒、超时、获取锁成功)
  • Running → Terminated

五、守护线程 & 用户线程区别

1. 定义

  • 用户线程:业务工作线程,JVM 必须等全部用户线程结束才退出
  • 守护线程:后台服务线程,用户线程全部结束,JVM直接退出,无视守护线程

2. 使用方式

thread.setDaemon(true); // 必须在 start() 之前设置

3. 应用场景

  • GC 垃圾回收线程
  • 后台心跳、监控、日志异步上报
  • 闲置资源清理任务

六、sleep () /wait () /yield () /join () 核心区别

1. sleep()

  • 属于 Thread 方法
  • 不释放锁
  • 限时休眠,时间到自动唤醒
  • 进入 TIMED_WAITING

2. wait()

  • 属于 Object 方法
  • 释放锁
  • 必须等待 notify/notifyAll 唤醒
  • 进入 WAITING

3. yield()

  • 属于 Thread 方法
  • 不释放锁
  • 主动让出 CPU,回到就绪态重新竞争
  • 效果不稳定、极少使用

4. join()

  • 属于 Thread 方法
  • 当前线程等待目标线程执行完毕
  • 底层基于 wait 实现

口诀

  • sleep:抱着锁睡觉
  • wait:释放锁等人喊
  • yield:礼让 CPU
  • join:等别人干完

七、为什么 wait/notify 必须放在 synchronized 中?

  1. 必须持有对象监视器锁
    JVM 规范规定:执行 wait/notify 必须获取当前对象锁,否则直接抛出 IllegalMonitorStateException
  2. 防止唤醒丢失(核心)
    不加锁会出现:条件判断通过瞬间,被其他线程修改,导致线程永久休眠、唤醒失效。
  3. 保证原子性
    解决「判断 - 等待」并发竞态问题。

总结

wait/notify 是基于监视器锁的等待唤醒机制,必须同步保证安全。

八、线程上下文切换是什么?为什么耗性能?

1. 什么是上下文切换

CPU 从线程 A 切到线程 B:

  1. 保存当前线程上下文(栈、PC 寄存器、状态)
  2. 加载新线程上下文
    该过程就是 线程上下文切换

2. 为什么耗时?

  1. 需要用户态切换内核态,系统开销大
  2. 频繁保存 / 恢复线程现场
  3. CPU 缓存失效,命中率下降
  4. 线程越多,切换越疯狂,CPU 利用率越低

结论

高并发不是线程越多越好,线程过多会导致频繁上下文切换,拖垮系统。

面试速记总结

  1. 进程是资源单位,线程是调度单位
  2. Go 协程用户态轻量、Java 线程内核态重量级
  3. 线程三种创建:Thread、Runnable、Callable
  4. 线程五状态:新建、就绪、运行、阻塞、终止
  5. 守护线程随 JVM 退出,用户线程必须执行完
  6. sleep 不释放锁、wait 释放锁
  7. wait/notify 必须加锁防止并发错乱
  8. 上下文切换保存现场、消耗 CPU 资源
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐