一、多线程基础概念
- 进程与线程的区别
- 进程中可能包含多个线程,线程是CPU调度的基本单位。
- 线程创建方式(Thread/Runnable/Callable+线程池)
- 根本上都是使用Runnable接口创建,使用Thread类启动。
- 线程生命周期与状态转换(NEW/RUNNABLE/BLOCKED/WAITING/TIMED_WAITING/TERMINATED)
- 守护线程与用户线程的区别
- 守护线程服务于用户线程,当用户线程全都销毁后自动销毁,JVM退出时无需等待守护线程执行完成。用户线程全部执行完毕且销毁后JVM才会退出。
- 通过
setDaemon(true)设置,需在start()前调用。
二、Java多线程核心类与API
- Thread类
- start()与run()区别
- start()会启动一个线程执行run()方法的内容。
- run()只是一个普通方法,不会新建线程执行。
- sleep() vs yield() vs join()
- sleep()会让线程休眠。
- yield()只会让出CPU时间片,不会休眠。
- join()会让当前线程等待join的线程执行完毕才开始执行。
- interrupt()中断机制
- start()与run()区别
- synchronized关键字
- 非公平锁
- 对象锁与类锁
- 同步方法与同步代码块
- 当加在静态方法上时会使用类锁。
- 加在成员方法上时会锁住当前对象。
- 加在同步代码块上以指定的对象为锁,使用class对象即为类锁,使用普通对象即为对象锁。
- 锁升级过程(无锁->偏向锁→轻量级锁→重量级锁)
- 对象创建时为无锁状态。
- 有一个线程获取锁时,将锁对象的对象头中锁标志置为偏向锁,线程ID为当前线程。
- 当再来另外一个线程获取锁时,会升级为轻量级锁,CAS自旋获取锁。
- 当锁竞争比较激烈,即自旋达到一定次数没有获取到锁时会升级为重量级锁,未获取到锁的线程会进入阻塞状态。
- wait()/notify()机制
- 生产者-消费者模型实现
- 生产队列满了,生产者调用wait()等待消费者消费并通过notify()唤醒生产者。
- 虚假唤醒问题与解决方案
- 需用
while循环检查条件而非if
- 需用
- 生产者-消费者模型实现
三、java.util.concurrent并发工具包
-
锁机制
ReentrantLock(公平锁/非公平锁)- 支持公平锁和非公平锁两种状态,在创建锁时可以通过构造函数的参数进行指定。
ReadWriteLock(读写锁)- 读写分离,同时可以有多个线程获取读锁。
- 读锁与写锁互斥,读锁不能升级为写锁,但写锁能够降级为读锁,通过获取读锁,释放写锁完成降级。
Condition条件队列ReentrantLock支持同时具有多个Condition条件队列,更加灵活。
-
原子类(AtomicXXX)
- CAS原理与ABA问题
- CAS通过先比较再交换保证线程安全。
- ABA问题是CAS获取到的A是修改成B又修改成A的那个A,解决办法是加递增版本号(
AtomicStampedReference类提供了解决办法)。
LongAdder高性能实现
- CAS原理与ABA问题
-
线程池
-
ThreadPoolExecutor核心参数(corePoolSize/maxPoolSize/workQueue/rejectPolicy)corePoolSize:核心线程数,常驻在线程池,但是也可以通过设置allowCoreThreadTimeOut参数为true让核心线程可以被回收,默认为false。maxPoolSize:最大线程数,最多可以创建的线程数量workQueue:任务队列,当核心线程都在执行任务时,将任务暂存在任务队列中。RejectedExecutionHandler:拒绝策略,AbortPolicy(抛异常)、CallerRunsPolicy(调用者执行)、DiscardOldestPolicy(丢弃最旧任务)、DiscardPolicy(静默丢弃)
-
线程池工作流程与状态转换
- 任务提交 → 核心线程满则入队 → 队列满则创建新线程 → 超过最大线程则触发拒绝策略
-
Executors工具类(newCachedThreadPool/newFixedThreadPool风险)-
newCachedThreadPool可能会创建过多的线程-
1. 核心参数
- 核心线程数:0
- 最大线程数:
Integer.MAX_VALUE - 队列类型:
SynchronousQueue(无容量缓冲队列,生产者插入需等待消费者消费) - 线程存活时间:60秒(空闲线程超时回收)
2. 适用场景
- 短期突发任务:如瞬时高并发请求(例如秒杀系统的瞬时流量处理)。
- 异步IO密集型任务:如HTTP短连接请求、文件下载等,任务执行时间短且线程复用率高。
3. 风险与缺陷
-
线程爆炸:任务提交速度远超处理速度时,可能创建大量线程(理论可达
Integer.MAX_VALUE),导致系统资源(CPU/内存)耗尽。
-
上下文切换开销:线程数过多时,频繁的线程切换会降低CPU利用率,甚至触发系统熔断。
-
不适用长耗时任务:线程长期占用可能导致队列积压和拒绝策略失效。
-
-
newFixedThreadPool可能会在队列中存入太多的任务导致内存溢出-
核心参数
- 核心线程数:用户指定
- 最大线程数:与核心线程数相同
- 队列类型:
LinkedBlockingQueue(无界队列,默认容量Integer.MAX_VALUE)
2. 适用场景
- 稳定并发任务:如后台定时任务处理、日志批量写入等,线程数固定且任务量可控的场景。
- CPU密集型任务:避免线程过多导致的上下文切换损耗。
3. 风险与缺陷
- 内存溢出(OOM):无界队列可能无限堆积任务,导致内存耗尽(例如电商系统秒杀活动时订单积压)。
- 响应延迟:队列过长时,新任务需等待较长时间才能被处理。
- 线程僵死:若线程因异常终止且未重启,可能导致任务堆积但无可用线程处理
-
-
-
-
并发集合类
- ConcurrentHashMap(JDK7分段锁 vs JDK8 CAS+sychronized),03/25已总结
- CopyOnWriteArrayList适用场景
- 适用于读多写少的场景,03/25已总结
- BlockingQueue实现类(Array/Linked/ Priority/Synchronous)
四、线程通信与协作
- CountDownLatch(一次性屏障):一次性屏障,主线程等待N个子任务完成
- CyclicBarrier(可重用屏障):可重复使用,所有线程到达屏障后继续执行
- Semaphore(信号量模型):用于控制并发线程数,如数据库连接池限制
五、高级主题
- volatile关键字
- 内存可见性原理(MESI协议)
- 通过内存屏障禁止缓存,直接读写主内存
- 禁止指令重排序(内存屏障)
- 禁止指令重排序(单例模式双重检查锁配合
volatile)
- 禁止指令重排序(单例模式双重检查锁配合
- 内存可见性原理(MESI协议)
- ThreadLocal
- 原理与内存泄漏问题
- 每个线程独立存储数据,通过
ThreadLocalMap实现(Key为弱引用防止内存泄漏) - 需手动调用
remove()清理Entry
- 每个线程独立存储数据,通过
- 原理与内存泄漏问题
- Java内存模型(JMM)
- 主内存与工作内存
- happens-before规则
- 原子性/可见性/有序性保证
六、多线程设计模式
- 生产者-消费者模式(BlockingQueue实现)
- Thread-Per-Message模式
- Worker Thread模式(线程池应用)
- Balking模式(任务丢弃策略)
- Guarded Suspension模式(条件等待)
七、性能优化与调试
- 线程安全的最佳实践
- 无状态对象
- 不可变对象
- 线程封闭(栈封闭/ThreadLocal)
- 死锁排查与解决
- jstack诊断死锁
- 锁顺序化解决方案
- 性能优化策略
- 减少锁粒度(ConcurrentHashMap分桶)
- 无锁编程(CAS/原子类)
- 避免伪共享(@Contended注解)
八、常见面试题专题
- synchronized与ReentrantLock的区别
- ThreadLocal内存泄漏原因与解决方案
- 线程池任务执行流程与拒绝策略
- ConcurrentHashMap扩容机制(JDK8)
- 如何实现线程安全单例模式(DCL+volatile)
- volatile能否保证原子性?
- 如何排查高CPU占用线程?
九、扩展知识
-
Java8+新特性
- CompletableFuture异步编程
- StampedLock(乐观读锁)
-
分布式并发问题
- 分布式锁(Redis/ Zookeeper)
- 数据库锁机制(乐观锁/悲观锁)
-
高并发设计
- 限流算法(令牌桶/漏桶)
- 熔断降级策略
-
LeetCode 34.在排序数组中查找元素的第一个和最后一个位置 2. 考察二分查找,二分查找需要注意边界