java面试中经常会问到的多线程问题有哪些(基础版)
文章目录
- 一、线程基础与生命周期
- 二、并发安全与同步机制
- 三、线程池核心问题
- 四、锁机制与并发工具
- 五、并发容器
- 六、实战场景问题
- 总结
Java 多线程是面试中的高频考点,涉及线程基础、并发安全、线程池、锁机制等核心内容。以下是面试中经常出现的多线程问题及核心考察点:
一、线程基础与生命周期
- 线程和进程的区别?
- 进程是资源分配的最小单位(拥有独立内存、CPU 等资源),线程是 CPU 调度的最小单位(共享进程资源)。
- 进程间切换开销大,线程间切换开销小;一个进程可包含多个线程。
- Java 中创建线程的方式有哪些?
- 继承
Thread
类(重写run()
方法)。- 实现
Runnable
接口(重写run()
方法,通过Thread
包装)。- 实现
Callable
接口(重写call()
方法,可返回结果,通过FutureTask
配合Thread
)。- 考察点:
Runnable
与Callable
的区别(返回值、异常处理)、为什么推荐实现接口而非继承Thread
(Java 单继承限制)。
- 线程的生命周期有哪些状态?状态之间如何转换?
- 6 种状态(JDK 定义):
NEW
(新建)→RUNNABLE
(就绪/运行)→BLOCKED
(阻塞,等待锁)→WAITING
(无限等待)→TIMED_WAITING
(超时等待)→TERMINATED
(终止)。- 关键转换:
wait()
使线程从RUNNABLE
→WAITING
(需notify()
唤醒);sleep(1000)
使线程 →TIMED_WAITING
(时间到后自动唤醒);争夺锁失败 →BLOCKED
(获取锁后回到RUNNABLE
)。
二、并发安全与同步机制
- 什么是线程安全?如何保证线程安全?
- 线程安全:多线程并发访问时,程序行为符合预期(结果正确、无数据混乱)。
- 保证方式:
synchronized
关键字、volatile
关键字、Lock
锁(ReentrantLock
)、原子类(AtomicInteger
)、线程封闭(ThreadLocal
)等。
synchronized
的实现原理?与Lock
的区别?
- 原理:基于 JVM 内置锁(监视器锁
monitor
),通过ACC_SYNCHRONIZED
标志(方法)或monitorenter/monitorexit
指令(代码块)实现。
- 区别:
| 维度 | `synchronized` | `Lock`(如 `ReentrantLock`) | |--------------|--------------------------------|--------------------------------------| | 锁释放 | 自动释放(异常或方法结束) | 需手动 `unlock()`(通常在 `finally` 中) | | 灵活性 | 不可中断、不可超时 | 支持中断、超时、公平锁 | | 条件变量 | 仅通过 `wait()`/`notify()` | 支持多条件变量(`Condition`) |
volatile
的作用?能保证原子性吗?
- 作用:保证变量的可见性(一个线程修改后,其他线程立即可见)和禁止指令重排序(如单例模式的双重检查锁需用
volatile
修饰实例)。- 不能保证原子性:例如
i++
(读-改-写三步操作),多线程下可能出现数据不一致,需配合synchronized
或原子类。
ThreadLocal
的原理?可能导致什么问题?
- 原理:每个线程有独立的
ThreadLocalMap
,存储线程私有变量(键为ThreadLocal
实例,值为变量),实现线程隔离。- 问题:线程池复用线程时,若
ThreadLocal
未及时remove()
,会导致变量被复用(内存泄漏);底层Entry
是弱引用(Key
弱引用,Value
强引用),可能导致Value
无法回收。
三、线程池核心问题
- 线程池的核心参数有哪些?工作原理是什么?
- 核心参数:
corePoolSize
(核心线程数)、maximumPoolSize
(最大线程数)、keepAliveTime
(非核心线程空闲存活时间)、workQueue
(任务队列)、threadFactory
(线程工厂)、handler
(拒绝策略)。- 工作原理:
1. 任务提交时,若核心线程未满,创建核心线程执行任务;
2. 核心线程满,任务放入队列;
3. 队列满,创建非核心线程执行任务;
4. 总线程数达最大线程数,触发拒绝策略(如AbortPolicy
抛出异常)。
- 线程池的拒绝策略有哪些?
AbortPolicy
(默认):直接抛出RejectedExecutionException
。CallerRunsPolicy
:让提交任务的线程自己执行(缓解压力)。DiscardPolicy
:默默丢弃任务。DiscardOldestPolicy
:丢弃队列中最旧的任务,再尝试提交当前任务。
- 如何合理配置线程池参数?
- CPU 密集型任务(如计算):核心线程数 = CPU 核心数 + 1(减少线程切换开销)。
- IO 密集型任务(如网络请求、数据库操作):核心线程数 = CPU 核心数 × 2(利用 IO 等待时间并行处理)。
- 关键:结合任务类型(CPU/IO 密集)、队列容量(避免过大导致 OOM)、拒绝策略(根据业务容忍度选择)。
四、锁机制与并发工具
- 什么是死锁?如何避免死锁?
- 死锁:两个或多个线程互相持有对方需要的锁,无限等待(如线程 A 持有锁 1 等待锁 2,线程 B 持有锁 2 等待锁 1)。
- 避免:固定锁的获取顺序、使用
tryLock(timeout)
超时获取锁、使用Lock
的lockInterruptibly()
响应中断、定期检测死锁(ThreadMXBean
)。
- 公平锁与非公平锁的区别?
ReentrantLock
默认是哪种?
- 公平锁:线程获取锁的顺序按请求顺序(FIFO),避免饥饿,但性能较低。
- 非公平锁:允许“插队”(刚释放锁的线程可再次获取锁),性能高,但可能导致线程饥饿。
ReentrantLock
默认是非公平锁(通过构造函数new ReentrantLock(true)
开启公平锁)。
- Java 中的原子类(如
AtomicInteger
)如何保证原子性?
- 基于 CAS(Compare And Swap,比较并交换)操作:通过 Unsafe 类的 native 方法,直接操作内存,实现无锁的原子更新(如
compareAndSet(expected, update)
)。- 问题:ABA 问题(可通过
AtomicStampedReference
加版本号解决)、循环时间长导致 CPU 开销大。
CountDownLatch
、CyclicBarrier
、Semaphore
的区别?
CountDownLatch
:一个线程等待其他 N 个线程完成(倒计时,不可重置)。CyclicBarrier
:N 个线程互相等待,全部到达后一起执行(可重置,支持回调)。Semaphore
:控制同时访问资源的线程数(如限制并发连接数)。
五、并发容器
HashMap
为什么线程不安全?ConcurrentHashMap
如何实现线程安全?
HashMap
线程不安全:多线程扩容时可能出现链表环(JDK 7),或put
时覆盖数据。ConcurrentHashMap
(JDK 1.8):采用“数组 + 链表/红黑树”,通过 CAS +synchronized
同步链表头节点实现线程安全(粒度比 JDK 7 的分段锁更细,性能更高)。
ArrayList
和Vector
的区别?CopyOnWriteArrayList
适合什么场景?
Vector
线程安全(方法加synchronized
),但性能低;ArrayList
线程不安全。CopyOnWriteArrayList
:写操作时复制底层数组(add
/set
会创建新数组),读操作无锁,适合读多写少场景(如配置缓存),但存在数据一致性延迟和内存开销。
六、实战场景问题
- 如何实现一个生产者-消费者模型?
- 基于
synchronized
+wait()
/notify()
:用队列存储任务,生产者put
时通知消费者,消费者take
时等待(队列空)。- 基于
Lock
+Condition
:更灵活(生产者和消费者可使用不同Condition
唤醒)。- 基于阻塞队列(
ArrayBlockingQueue
):简化实现(队列自带阻塞功能)。
- 单例模式的线程安全实现?双重检查锁为什么需要
volatile
?
- 线程安全单例:饿汉式(类加载时初始化)、懒汉式(
synchronized
方法)、双重检查锁(DCL)、静态内部类。- DCL 中的
volatile
:防止指令重排序(实例化对象的步骤:分配内存 → 初始化 → 赋值,重排序可能导致其他线程获取到未初始化的实例)。
- 线程池中的线程抛出异常会怎样?如何处理?
- 若任务通过
execute()
提交:异常会直接抛出,线程池会销毁该线程并新建线程。- 若通过
submit()
提交:异常会被封装到Future
中,需调用get()
才能获取异常。- 处理:重写线程池的
afterExecute()
方法,或在任务内部try-catch
捕获异常。
总结
多线程面试重点考察基础概念理解(线程状态、锁机制)、并发安全保障(
synchronized
、volatile
、原子类)、线程池原理与配置、实战问题解决能力(死锁、生产者-消费者)。回答时需结合底层原理(如 CAS、监视器锁)和实际场景(如线程池参数配置依据),体现对并发编程的深度理解。