一、JUC介绍
1. 为什么需要JUC?
- 传统并发缺陷:
synchronized
关键字存在性能瓶颈(重量级锁)、缺乏灵活性(不可中断、非公平)。 - JUC优势:
- 提供更细粒度的锁控制(如
ReentrantLock
的可中断、超时获取锁)。 - 非阻塞算法提升性能(如
CAS
替代锁竞争)。 - 丰富的并发工具(线程池、并发容器、同步器等)。
2. JUC的设计思想
- 分工:通过线程池管理线程资源(如
ThreadPoolExecutor
)。 - 协作:利用同步工具协调线程执行顺序(如
CountDownLatch
)。 - 互斥:通过锁和原子类保证线程安全(如
ReentrantLock
、AtomicInteger
)。
二、JUC重点
1. 锁机制:AQS(AbstractQueuedSynchronizer)
核心原理
- CLH队列:通过双向链表实现线程排队,避免自旋导致的CPU浪费。
- 状态变量(state):通过
volatile
修饰的int
值表示锁状态(如重入次数)。 - 模板方法模式:子类实现
tryAcquire
/tryRelease
定义锁获取和释放逻辑。
面试高频问题
2. 显式锁:ReentrantLock vs synchronized
对比维度 | ReentrantLock | synchronized |
---|
锁获取方式 | 需手动lock() /unlock() | 自动获取和释放(代码块或方法) |
可中断性 | 支持(lockInterruptibly() ) | 不支持 |
公平性 | 支持公平与非公平(构造函数指定) | 仅非公平 |
条件变量 | 支持多条件(newCondition() ) | 单一等待队列 |
性能 | 高并发场景更优 | 优化后(偏向锁、轻量级锁)性能接近 |
使用示例
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();lock.lock();
try {while (queue.isEmpty()) {condition.await(); }
} finally {lock.unlock();
}
3. 并发容器
ConcurrentHashMap
- JDK 7 vs JDK 8:
- JDK7:分段锁(Segment),每个段独立加锁。
- JDK8:
CAS + synchronized
锁单个链表头节点,粒度更细。
- 关键方法:
putVal()
:通过spread(hash)
计算索引,CAS插入节点。size()
:基于CounterCell
的分段统计,避免竞争。
CopyOnWriteArrayList
- 适用场景:读多写少(如监听器列表)。
- 实现原理:写操作时复制新数组,替换旧引用(保证最终一致性)。
4. 线程池(ThreadPoolExecutor)
核心参数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler
)
任务处理流程
- 提交任务,若核心线程未满,创建新线程执行。
- 核心线程已满,任务进入阻塞队列。
- 队列已满,创建非核心线程执行。
- 线程数达最大值且队列满,触发拒绝策略。
拒绝策略
- AbortPolicy(默认):抛出
RejectedExecutionException
。 - CallerRunsPolicy:由提交任务的线程直接执行。
- DiscardOldestPolicy:丢弃队列最旧任务,重试提交。
- DiscardPolicy:静默丢弃新任务。
5. 同步工具类
CountDownLatch vs CyclicBarrier
对比项 | CountDownLatch | CyclicBarrier |
---|
重置性 | 一次性 | 可重复使用 |
等待动作 | 主线程等待其他线程完成 | 所有线程相互等待 |
核心方法 | countDown() + await() | await() |
典型场景 | 启动准备、任务分阶段提交 | 多线程计算后合并结果 |
Semaphore
三、JUC面试题
1. volatile关键字的作用?
- 可见性:保证变量修改后立即同步到主内存。
- 禁止指令重排序:通过内存屏障实现(如单例模式的双重检查锁)。
2. CAS的ABA问题如何解决?
- 版本号机制:使用
AtomicStampedReference
记录变量版本。
3. 线程池如何避免内存泄漏?
- 务必关闭线程池:调用
shutdown()
或shutdownNow()
。 - 使用有界队列:避免任务无限堆积导致OOM。