02 并发与线程池

这一块在面试里是干什么的

主要判断你:

  • 会不会写并发代码
  • 是否知道线程安全风险
  • 出问题时会不会分析死锁、竞争、性能瓶颈

进程和线程

  • 进程:资源分配的基本单位
  • 线程:CPU 调度的基本单位

一句话:

“一个进程里可以有多个线程,线程共享进程资源。”

并发和并行

  • 并发:看起来同时执行,宏观同时
  • 并行:真正同时执行,微观同时

线程创建方式

常见说法:

  • 继承 Thread
  • 实现 Runnable
  • 实现 Callable
  • 线程池

面试里推荐强调:

“实际开发优先使用线程池,而不是手动频繁创建线程。”

为什么要用线程池

它解决的问题:

  • 减少线程创建销毁开销
  • 控制并发数量
  • 避免系统被打爆
  • 统一管理线程生命周期

线程池核心参数

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

高频面试题:

线程池工作流程

  1. 先看核心线程是否满
  2. 没满就创建核心线程执行
  3. 满了就进队列
  4. 队列满了再尝试创建非核心线程
  5. 还满就触发拒绝策略

常见拒绝策略

  • 直接抛异常
  • 调用者自己执行
  • 丢弃任务
  • 丢弃最旧任务

什么是线程安全

多个线程同时访问同一份数据,结果仍然正确。

synchronized

最基础的线程同步手段。

它的作用:

  • 保证同一时间只有一个线程进入临界区
  • 保证可见性
  • 保证有序性的一部分

volatile

它保证:

  • 可见性
  • 一定程度上的有序性

它不保证:

  • 原子性

高频快答:

volatile 适合一个线程写、多个线程读的状态标记,不适合 i++ 这种复合操作。”

什么是原子性

一个操作要么全部完成,要么全部不完成,中间不会被打断。

什么是可见性

一个线程修改变量后,其他线程能立刻看到最新值。

什么是有序性

程序执行顺序在单线程看起来正常,但多线程里可能因为指令重排出现问题。

Locksynchronized 区别

先记简单版:

  • synchronized:关键字,使用简单,自动释放锁
  • Lock:接口,更灵活,可手动加锁解锁,支持更多特性

ReentrantLock

可重入锁。

可重入的意思:

“同一个线程拿到锁后,可以再次进入同一把锁保护的代码。”

什么是死锁

两个或多个线程互相等待对方释放资源,导致都无法继续。

死锁四个条件先知道名字:

  • 互斥
  • 请求并持有
  • 不可剥夺
  • 循环等待

如何避免死锁

  • 固定加锁顺序
  • 缩小锁范围
  • 尽量避免嵌套锁
  • 使用超时机制

ThreadLocal

不是用来解决共享,而是给每个线程一份自己的变量副本。

常见场景:

  • 用户上下文
  • traceId
  • 数据库连接上下文

常见坑:

  • 线程池复用线程,如果不清理可能脏数据泄漏

sleep()wait()

  • sleep()Thread 的方法,不释放锁
  • wait()Object 的方法,释放锁,需要配合 synchronized

RunnableCallable

  • Runnable:没有返回值
  • Callable:有返回值,可以抛异常

并发容器先会说几个

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • BlockingQueue

一句话:

“并发容器适合多线程场景,比自己手写锁更安全省事。”

高频快答

为什么不建议直接 new 线程池

因为默认参数可能不合理,容易导致资源耗尽。实际开发更建议显式指定核心参数。

ConcurrentHashMap 为什么比 HashMap 更适合并发

因为它是线程安全的,并发控制更细,性能更好。

i++ 为什么不是线程安全

因为它不是一个原子操作,包含读、改、写三个步骤。

这一章最小记忆包

  • 生产代码优先线程池
  • 线程池重点记 5 个参数和工作流程
  • synchronized 保证同步,volatile 不保证原子性
  • 线程安全核心是原子性、可见性、有序性
  • ThreadLocal 是线程私有,不是共享
  • 死锁要会定义、原因、规避方式