Java多线程相关面试题|全面解析+实战示例+高频考点
本页面系统梳理java多线程相关面试题核心知识点,覆盖线程生命周期、线程同步机制、线程池原理、volatile与synchronized对比、死锁检测与预防、并发工具类使用、JMM内存模型等关键内容,并提供大量真实代码示例与解题思路,助您高效应对大厂技术面试!
一、线程基础概念与生命周期
1.1 线程与进程的区别?
线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位;而进程是操作系统分配资源的基本单位。
- 资源分配:进程是资源分配的基本单位;线程共享所属进程的资源
- 调度单位:线程是CPU调度的基本单位(轻量级进程)
- 通信方式:进程间通信需借助IPC(管道、消息队列等);线程间可直接读写共享内存
- 切换开销:线程上下文切换比进程快,因共享大部分上下文
- 独立性:进程具有独立地址空间;线程共享地址空间,一个线程崩溃可能导致整个进程退出
【面试提示】:Java中可通过Thread类或ExecutorService创建线程,但本质都是JVM调用操作系统原生线程(1:1模型)。
1.2 Java中创建线程的三种方式?
方式一:继承Thread类
// 继承Thread类并重写run()方法
// 优点:简单直观;缺点:单继承限制
// 推荐:仅用于简单场景
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行中...");
}
}
// 启动线程
Thread t = new MyThread();
t.start();
方式二:实现Runnable接口
// 实现Runnable接口,将任务与线程分离
// 优点:避免单继承限制;可共享数据;推荐方式
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("任务执行中...");
}
}
// 启动线程
Thread t = new Thread(new MyTask());
t.start();
方式三:实现Callable接口 + FutureTask
// 支持返回值与异常处理
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 42;
}
}
// 包装并启动
FutureTask<Integer> future = new FutureTask<>(new MyCallable());
Thread t = new Thread(future);
t.start();
Integer result = future.get(); // 阻塞等待结果
Callable是JDK1.5新增的函数式接口,与Runnable类似但支持返回值和抛出受检异常。
1.3 线程的五种基本状态及转换?
| 状态 | 说明 | 触发条件 |
|---|---|---|
| 新建(NEW) | 线程对象已创建,但尚未调用start() | new Thread() |
| 就绪(RUNNABLE) | 调用start()后等待CPU调度 | 调用start();时间片用完;yield()后 |
| 运行(RUNNING) | 获得CPU时间片,执行run() | CPU调度到该线程 |
| 阻塞(BLOCKED) | 等待进入synchronized同步块/方法 | 竞争锁失败 |
| 等待(WAITING) | 无限期等待其他线程唤醒 | 调用wait()、join()、LockSupport.park() |
| 超时等待(TIMED_WAITING) | 有限期等待(超时自动返回) | 调用sleep(ms)、wait(timeout)、join(ms) |
| 终止(TERMINATED) | run()执行完毕或异常退出 | 正常结束;抛出未捕获异常 |
Java中RUNNABLE状态实际包含操作系统层面的RUNNING和READY,因为JVM不区分二者。
1.4 sleep()与wait()的区别?
- 属于
Thread类的静态方法 - 不释放对象锁(仍持有monitor)
- 必须处理
InterruptedException - 指定时间后自动恢复(超时等待)
- 可在任何地方调用(无需同步上下文)
- 属于
Object类的实例方法 - 释放对象锁(释放monitor)
- 必须在同步块/方法中调用(否则IllegalMonitorStateException)
- 需其他线程调用notify()/notifyAll()唤醒(等待状态)
- 可带超时参数(wait(timeout))
// 错误示例:wait()不在同步块中
// Object obj = new Object();
// obj.wait(); // 抛出IllegalMonitorStateException
// 正确示例
synchronized (obj) {
obj.wait(); // 释放锁,进入等待队列
// ...
obj.notify(); // 唤醒一个等待线程
}
二、线程同步与锁机制
2.1 synchronized的底层原理?
synchronized是Java关键字,用于实现互斥同步,其底层依赖JVM的monitor对象(每个对象头含monitor指针)。
- 进入synchronized块前,执行
monitorenter指令获取monitor - 退出时执行
monitorexit指令释放monitor - 异常退出时JVM自动释放monitor
锁状态升级流程:
JDK1.6后默认开启偏向锁(可通过-XX:-UseBiasedLocking关闭),锁升级是单向过程,不可逆。
2.2 volatile的可见性与禁止指令重排?
volatile是轻量级同步机制,保证:
- 可见性:线程修改后立即写回主内存,其他线程读取时强制从主内存刷新
- 禁止指令重排:通过插入内存屏障(Memory Barrier)实现
| 屏障类型 | 作用 |
|---|---|
LoadLoad |
禁止上方读操作与下方读操作重排 |
StoreStore |
禁止上方写操作与下方写操作重排 |
LoadStore |
禁止上方读操作与下方写操作重排 |
StoreLoad |
禁止上方写操作与下方读操作重排(开销最大) |
典型应用:双重检查锁定单例
// 正确实现:volatile防止指令重排
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
若无volatile,new Singleton()可能被重排为:
① 分配内存 → ② 设置引用 → ③ 初始化对象
导致其他线程获取未初始化完成的对象!
2.3 ReentrantLock与synchronized对比?
- 可中断锁:支持
lockInterruptibly()响应中断 - 公平锁:构造时指定
new ReentrantLock(true) - 尝试获取锁:
tryLock()避免无限等待 - 条件变量:
newCondition()替代wait/notify - 性能优化:JDK1.6后synchronized性能提升,但高竞争下ReentrantLock更优
选择建议:
- 简单同步场景 → synchronized(JVM自动优化)
- 需要中断/超时/公平性 → ReentrantLock
- 读多写少 → ReadWriteLock(如
StampedLock)
// 资源竞争避免死锁
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
void transfer(Account from, Account to, int amount) {
while (true) {
if (lock1.tryLock()) {
try {
if (lock2.tryLock()) {
try {
from.debit(amount);
to.credit(amount);
return;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
// 竞争失败,短暂等待后重试
Thread.sleep(10);
}
}
2.4 乐观锁与悲观锁?
悲观锁:假设共享数据总是被修改,全程加锁(如synchronized、ReentrantLock)
乐观锁:假设共享数据很少冲突,仅在更新时检查版本号(如CAS)
Compare-And-Swap,通过CPU指令实现原子操作
// AtomicInteger内部使用CAS
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
CAS可能因值重复导致误判,解决方案:
- 使用
AtomicStampedReference添加版本戳 - 使用
AtomicMarkableReference标记位
三、线程池与并发工具类
3.1 线程池核心参数解析?
使用ThreadPoolExecutor构造线程池:
new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 非核心线程空闲存活时间
unit, // 时间单位
workQueue, // 任务队列
threadFactory, // 线程工厂
handler // 拒绝策略
);
- 线程数 < corePoolSize → 直接创建新线程
- 线程数 ≥ corePoolSize & 队列未满 → 加入队列等待
- 队列满 & 线程数 < maximumPoolSize → 创建新线程
- 线程数 = maximumPoolSize & 队列满 → 执行拒绝策略
| 策略 | 行为 | 适用场景 |
|---|---|---|
AbortPolicy |
抛出RejectedExecutionException |
默认策略,快速失败 |
CallerRunsPolicy |
由调用线程执行任务 | 允许延迟执行,不丢弃任务 |
DiscardPolicy |
直接丢弃任务 | 允许任务丢失的场景 |
DiscardOldestPolicy |
丢弃队列头任务,插入新任务 | 高优先级任务场景 |
3.2 常见任务队列类型?
- 基于链表实现,容量默认
Integer.MAX_VALUE - 吞吐量高,适合生产者-消费者模型
- 内存占用大,可能OOM
- 基于数组实现,容量固定
- 内存连续,访问快,适合固定负载
- 需预估容量,避免扩容开销
- 不存储元素,每个插入操作需等待删除操作
- 直接传递任务,适合高并发任务分发
- 需配合
cachedThreadPool使用
- IO密集型 →
LinkedBlockingQueue+corePoolSize = CPU核心数 * 2 - CPU密集型 →
ArrayBlockingQueue+corePoolSize = CPU核心数 + 1 - 任务直接传递 →
SynchronousQueue
3.3 Fork/Join框架原理?
Fork/Join是JDK7引入的并行计算框架,采用Work-Stealing算法:
- 每个线程有双端队列(Deque)
- 线程从队列头取任务(LIFO)
- 空闲线程从其他队列尾偷任务(FIFO)
// 继承RecursiveTask实现分治任务
class SumTask extends RecursiveTask<Long> {
private final long[] array;
private final int start, end;
public SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 阈值:单线程直接计算
if (end - start <= 10000) {
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
}
// 分治:拆分为两个子任务
int mid = (start + end) / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
invokeAll(left, right);
return left.join() + right.join();
}
}
// 使用
ForkJoinPool pool = new ForkJoinPool();
long[] data = new long[1000000];
Arrays.fill(data, 1L);
long result = pool.invoke(new SumTask(data, 0, data.length));
System.out.println("Sum: " + result);
- 任务拆分粒度需合理(太细导致调度开销大)
- 使用
join()而非get()避免阻塞 - 适合递归分治问题(如归并排序、MapReduce)
3.4 并发工具类对比?
| 工具类 | 核心功能 | 典型场景 |
|---|---|---|
CountDownLatch |
倒计时器,等待多个线程完成 | 主线程等待子任务初始化完成 |
CyclicBarrier |
循环栅栏,多线程相互等待 | 多线程协作计算,每轮同步 |
Semaphore |
信号量,控制并发线程数 | 数据库连接池限流 |
Exchanger |
线程间交换数据 | 生产者-消费者数据交换 |
Phaser |
动态阶段同步 | 多阶段任务分组同步 |
- 方向不同:CountDownLatch是“等待别人完成”;CyclicBarrier是“大家一起完成”
- 可重用性:CountDownLatch一次性;CyclicBarrier可重置复用
- 触发方式:CountDownLatch由其他线程调用countDown();CyclicBarrier由自身线程调用await()
四、并发集合与线程安全容器
4.1 常见并发集合对比?
| 集合 | 线程安全 | 适用场景 | 性能特点 |
|---|---|---|---|
Vector |
是(synchronized) | 已过时,不推荐 | 全方法同步,性能差 |
Collections.synchronizedList |
是(包装同步) | 简单场景 | 迭代需手动加锁 |
CopyOnWriteArrayList |
是(写时复制) | 读多写少(如监听器列表) | 写操作成本高 |
ConcurrentHashMap |
是(CAS + synchronized) | 高并发哈希表 | JDK8+分段锁优化 |
ConcurrentSkipListMap |
是(跳表) | 需要排序的并发Map | O(log n)查找 |
- 取消Segment分段锁,采用Node数组 + CAS + synchronized
- 红黑树优化:链表长度>8且数组>64时转红黑树
- 树节点减少到6时转回链表
// 写操作:复制新数组,替换原数组
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
读操作远多于写操作(如配置列表、事件监听器)
4.2 ConcurrentLinkedQueue与LinkedBlockingQueue?
- 无界非阻塞队列(基于CAS)
- 高吞吐量,适合高并发生产者/消费者
- 不支持size()实时获取(弱一致性)
- 无容量限制,可能OOM
- 可选有界/无界阻塞队列
- 支持阻塞式take()、put()
- 线程安全,适合低并发场景
- 内部使用ReentrantLock保护
- 高吞吐、非阻塞 → ConcurrentLinkedQueue
- 需要阻塞、背压控制 → LinkedBlockingQueue
五、JMM内存模型与happens-before规则
5.1 JMM核心概念?
Java内存模型(JMM)定义了线程与主内存的抽象关系:
- 主内存:所有变量存储位置
- 工作内存:每个线程私有内存,保存主内存变量副本
- 内存交互操作:read、load、use、assign、store、write、lock、unlock
- 线程A将变量从主内存→工作内存(read+load)
- 线程A修改变量(use+assign)
- 线程A将变量写回主内存(store+write)
- 线程B从主内存读取变量(read+load)
- 所有变量存储在主内存
- 线程间变量传递需通过主内存
- 线程对变量的读写操作在工作内存
5.2 happens-before规则?
happens-before定义了操作间的可见性关系:
| 规则 | 说明 | 示例 |
|---|---|---|
| 程序顺序规则 | 一个线程内,按代码顺序 | a → b(a在b前执行) |
| 监视器锁规则 | unlock() happens-before后续lock() | synchronized { x=1; } → synchronized { print(x); } |
| volatile规则 | 写happens-before后续读 | volatile x=1; → print(x); |
| 线程启动规则 | start() happens-before线程内操作 | thread.start(); → thread.run() |
| 线程终止规则 | 线程内操作happens-beforejoin() | thread.run() → thread.join() |
| 中断检测规则 | interrupt() happens-before检测 | thread.interrupt(); → thread.isInterrupted() |
| final域规则 | 构造函数结束happens-before读取 | new Obj(); → obj.field |
| 传递性 | A→B, B→C ⇒ A→C | x=1 → lock → unlock → y=x |
happens-before ≠ 执行顺序,仅保证内存可见性!
六、死锁检测与预防
6.1 死锁产生的四个必要条件?
互斥条件:资源不能被多个线程同时使用
请求与保持:线程已持有资源,又申请新资源
不可剥夺:资源只能由持有线程释放
循环等待:存在线程间的循环资源请求
- 破坏请求与保持:一次性申请所有资源
- 破坏不可剥夺:资源申请失败时释放已占资源
- 破坏循环等待:资源排序,按序申请
// 错误示例:可能死锁
void transfer1(Account a, Account b, int amount) {
synchronized (a) {
synchronized (b) {
a.debit(amount);
b.credit(amount);
}
}
}
// 正确示例:按账户ID排序
void transfer2(Account a, Account b, int amount) {
Account first = a.getId() < b.getId() ? a : b;
Account second = a.getId() < b.getId() ? b : a;
synchronized (first) {
synchronized (second) {
a.debit(amount);
b.credit(amount);
}
}
}
6.2 如何检测死锁?
jstack <pid>:查看线程堆栈,检查LOCKED状态jcmd <pid> Thread.print:打印线程信息- 使用JConsole/VisualVM图形化工具
- 代码中设置线程超时:
tryLock(timeout)
"Thread-1":
waiting to lock monitor 0x00007f8b4c004a20 (object 0x000000076b5c1d20, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f8b4c003c50 (object 0x000000076b5c1d30, a java.lang.Object),
which is held by "Thread-1"
- 统一资源获取顺序
- 使用超时机制(
tryLock) - 减少锁粒度
- 避免嵌套锁
七、ThreadLocal原理与陷阱
7.1 ThreadLocal工作原理?
ThreadLocal为每个线程提供变量副本,实现线程隔离:
// 线程本地存储
private final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
// 使用
threadLocal.set(10);
Integer value = threadLocal.get();
- Thread类中维护
ThreadLocalMap(每个线程一个) - ThreadLocal作为key,值为线程本地变量
- 哈希冲突使用开放地址法
- ThreadLocalMap的key是弱引用
- ThreadLocal对象被GC后,key为null
- 若线程长期存活,value无法回收 → 内存泄漏
- 使用后及时调用
remove() - 避免在线程池中使用(线程复用导致泄漏)
7.2 ThreadLocal vs synchronized?
- 以空间换时间:每个线程独占副本
- 无锁,无竞争
- 线程安全但非共享
- 以时间换空间:共享变量加锁
- 有锁,需等待
- 线程安全且共享
SimpleDateFormat线程不安全 → ThreadLocal包装- 数据库连接(Connection)线程隔离
- 用户上下文(UserContext)传递
八、原子变量与LongAdder
8.1 原子变量家族?
| 类型 | 类名 | 说明 |
|---|---|---|
| 基本类型 | AtomicInteger, AtomicLong, AtomicBoolean |
常用 |
| 数组 | AtomicIntegerArray, AtomicLongArray |
原子更新数组元素 |
| 引用 | AtomicReference |
原子更新对象引用 |
| 字段更新器 | AtomicIntegerFieldUpdater |
原子更新对象字段 |
// 原子更新用户对象
class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
AtomicReference<User> userRef = new AtomicReference<>(new User("Alice", 25));
User oldUser = userRef.get();
User newUser = new User("Bob", 30);
userRef.compareAndSet(oldUser, newUser);
8.2 LongAdder vs AtomicLong?
LongAdder是JDK8引入的高性能计数器:
- 分段累加:将计数分散到多个Cell,减少竞争
- 动态扩容:竞争激烈时自动增加Cell
- 高并发性能:比AtomicLong快3-10倍
| 特性 | AtomicLong | LongAdder |
|---|---|---|
| 竞争策略 | CAS重试 | 分段累加+求和 |
| 高并发性能 | 下降明显 | 保持稳定 |
| 内存占用 | 低 | 高(多Cell) |
| sum()开销 | 低 | 高(需遍历Cell) |
- 低竞争/计数小 → AtomicLong
- 高并发计数(如QPS统计) → LongAdder
- 需要实时精确值 → AtomicLong
九、常见并发设计模式
9.1 生产者-消费者模式?
使用阻塞队列实现解耦:
// 生产者
class Producer implements Runnable {
private final BlockingQueue<String> queue;
public Producer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
queue.put("Task-" + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 消费者
class Consumer implements Runnable {
private final BlockingQueue<String> queue;
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
String task = queue.take();
process(task);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void process(String task) {
System.out.println("Processing: " + task);
}
}
// 启动
BlockingQueue<String> queue = new LinkedBlockingQueue<>(10);
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
- 解耦:生产者与消费者独立演化
- 削峰:缓冲突发流量
- 异步:任务处理与提交分离
9.2 ReadWriteLock模式?
读写分离提升并发性能:
// 读写锁实现缓存
class Cache {
private final Map<String, Object> map = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public Object get(String key) {
lock.readLock().lock();
try {
return map.get(key);
} finally {
lock.readLock().unlock();
}
}
public void put(String key, Object value) {
lock.writeLock().lock();
try {
map.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
}
- 读操作远多于写操作
- 数据一致性要求高
- 写操作不频繁
十、高频面试问题精选
10.1 为什么wait()在Object中,而sleep()在Thread中?
wait()是锁级别的操作:需要释放对象锁,而锁属于对象,所以放在Object中;sleep()是线程级别的操作:仅暂停当前线程,不涉及锁操作,所以放在Thread中。
- 为什么notify()不释放锁? → 因为需要在同步块中执行后续操作
- 为什么sleep()不释放锁? → 设计为短暂暂停,避免锁丢失
10.2 如何安全地停止线程?
正确方式:使用中断机制(interrupt() + 检查isInterrupted())
class Worker implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println("Worker stopped");
}
}
Thread t = new Thread(new Worker());
t.start();
Thread.sleep(1000);
t.interrupt(); // 安全停止
stop():已废弃,可能导致数据不一致- 标志位(非volatile):可见性问题
10.3 线程池参数如何调优?
- 线程数 = CPU核心数 + 1
- 任务队列:ArrayBlockingQueue(容量=核心数*2)
- 拒绝策略:AbortPolicy
- 原因:CPU利用率高,避免上下文切换
- 线程数 = CPU核心数 * 2(或更高)
- 任务队列:LinkedBlockingQueue(容量大)
- 拒绝策略:CallerRunsPolicy(允许延迟)
- 原因:IO等待时间长,需更多线程
- 使用JMX监控线程池状态(activeCount、queueSize)
- 逐步调整,观察吞吐量与延迟
- 压测工具:JMeter/Gatling
十一、网友还关心:与java多线程相关面试题周边知识
11.1 JVM线程模型?
Java线程与操作系统线程映射关系:
- 1:1模型(主流):每个Java线程对应一个OS线程
- 历史模型:M:N(JDK1.2前,已废弃)
- 默认:-Xss1M(Linux)/ -Xss320K(Windows)
- 影响:栈越大,可创建线程数越少
- 调优:-Xss512k(高并发场景)
| 操作 | 时间(约) | 资源消耗 |
|---|---|---|
| 创建线程 | 50~100 μs | 栈内存 + 内核对象 |
| 上下文切换 | 1~10 μs | 寄存器 + 栈指针 |
| 线程调度 | 10~100 μs | 调度器队列操作 |
11.2 如何排查线程过多导致的OOM?
排查步骤:
jstat -gcutil <pid>:查看GC频率jstack <pid>:统计线程数(grep "java.lang.Thread.State")pmap <pid>:检查内存映射cat /proc/<pid>/status | grep Threads:线程数统计
- 使用线程池替代直接创建线程
- 限制线程池大小
- 设置线程栈大小(-Xss)
- 监控告警:线程数 > 500 触发预警
11.3 线程池与数据库连接池关系?
典型错误配置:
- 线程池大小 = 数据库连接数
- 导致:连接池耗尽,线程阻塞等待
正确配置:
- 线程池大小 = CPU核心数 * 2(应用层)
- 连接池大小 = 根据数据库配置调整(如MySQL建议50~100)
- 连接池与线程池解耦:使用连接池管理器
maximumPoolSize = min(30, (coreCount * 2) + 1)
connectionTimeout = 30000
idleTimeout = 600000
maxLifetime = 1800000
11.4 高并发系统设计原则?
- 资源管控:限制线程数避免OOM
- 任务排队:缓冲突发流量
- 拒绝策略:保护系统不被压垮
十二、学习资源推荐
12.1 必读书籍
- 《Java并发编程实战》(Brian Goetz)—— 并发圣经
- 《Java并发编程艺术》(方腾飞)—— 国内经典
- 《Java并发编程核心技巧》—— 实战案例丰富
- 《深入理解Java虚拟机》—— JMM深度解析
12.3 开源项目参考
- HikariCP:高性能数据库连接池(线程池设计典范)
- Netty:网络框架(事件循环线程模型)
- Akka:分布式系统(Actor模型并发)
- Apache Commons Pool:对象池设计
十三、常见误区与最佳实践
13.1 误区清单
- 误区1:volatile能保证原子性 → 实际仅保证可见性
- 误区2:线程池大小越大越好 → 忽略上下文切换开销
- 误区3:wait()和sleep()效果相同 → 锁释放行为不同
- 误区4:ConcurrentHashMap完全无锁 → JDK8仍用synchronized
- 误区5:线程安全 = 所有方法加synchronized → 过度同步影响性能
13.2 最佳实践
- 优先使用并发工具类:避免手写锁(易出错)
- 缩小锁范围:仅同步必要代码块
- 避免嵌套锁:统一加锁顺序
- 使用线程池:禁止直接new Thread()
- 监控线程状态:使用JMX/ Prometheus
- 诊断工具:jstack、jconsole、VisualVM
- 压测工具:JMeter、Gatling
- 监控平台:Prometheus + Grafana
- 日志框架:Logback异步Appender
十四、结语
本文系统梳理了java多线程相关面试题的核心知识点,覆盖从基础概念到高级应用的完整知识体系。通过真实代码示例、常见陷阱解析及高并发系统设计实践,帮助读者建立扎实的并发编程基础。
学习建议:
- 动手实践:每个概念都写代码验证
- 对比总结:synchronized vs volatile vs ReentrantLock
- 场景驱动:针对业务场景选择合适方案
- 持续更新:关注JDK新特性(如Project Loom)
欢迎访问www.kaoacca.cn获取更多技术资源!