跳转到内容

JUC并发编程

💡

JUC(java.util.concurrent)是Java并发编程的核心包,提供比synchronized更灵活强大的并发工具。本文涵盖volatile/CAS/AQS/线程池/并发容器/原子类/同步工具/CompletableFuture等核心知识点。


一、基础知识

1.1 并发三要素

要素

问题

解决方案

可见性

线程A修改的变量线程B看不到(CPU缓存)

volatile、synchronized、Lock、final

原子性

多条指令被打断(如i++读→改→写)

synchronized、Lock、Atomic类(CAS)

有序性

指令重排序导致意外结果

volatile(禁止重排)、synchronized(临界区内顺序)、final

1.2 JMM(Java内存模型)

📌

JMM定义多线程下共享变量访问规则。每个线程有本地内存(工作内存),操作共享变量时先拷贝到本地内存,写回主内存时间不确定。

  • 8种原子操作:lock/unlock(锁)、read/load/use/assign/store/write(读写流程)
  • happens-before原则:程序次序、volatile写-读、锁规则(unlock→lock)、传递性、线程start/join/interrupt规则等

二、volatile

2.1 三大特性

特性

机制

可见性

写volatile变量后立即刷新到主内存(store+write屏障);读之前从主内存加载(load+read屏障)。CPU缓存一致性协议(MESI)实现。

有序性

禁止指令重排序。LoadLoad屏障(读后不乱序)、StoreStore屏障(写前不乱序)、LoadStore屏障、StoreLoad屏障(最强,开销最大)。

不保证原子性

复合操作(i++)非原子:读→++→写,中间可能被其他线程打断。

2.2 DCL单例为何需要volatile

💡

new对象三步(可能重排序):(a)分配内存 → (b)初始化对象 → (c)引用指向内存。若无volatile,(c)可能在(b)之前执行,另一个线程在①处判断instance非null但对象未初始化完毕,返回半成品对象。volatile禁止此重排序。


三、CAS(Compare And Swap)

3.1 原理

📌

CAS(V, A, B):内存位置V,期望值A,新值B。仅当V的值=A时,将V更新为B并返回true;否则不修改返回false。通过CPU指令(lock cmpxchg)保证原子性。

3.2 CAS三大问题

问题

说明与解决

ABA问题

值从A→B→A,CAS检测不到中间变过。解决:AtomicStampedReference(版本号) / AtomicMarkableReference(布尔标记)

自旋开销

CAS失败后不断重试消耗CPU。解决:自旋次数上限、自适应自旋、升级为重量级锁

仅保证单变量原子性

不能同时修改多个变量。解决:AtomicReference包装对象 + 不可变对象(每次CAS整个替换)


四、synchronized 锁升级

JDK6起对synchronized进行大量优化,引入偏向锁→轻量级锁→重量级锁的锁升级机制(单向不可逆),大幅提升性能。

4.1 Mark Word

  • 对象头中的Mark Word存储:hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳

4.2 四种锁状态

锁状态

机制

无锁(01)

无竞争,直接访问

偏向锁(01+threadId)

对象"偏爱"第一个获取它的线程。该线程再次进入只需检查Mark Word中的Thread ID == 自身 → 直接获取。消除单线程重复加锁开销。

轻量级锁(00)

线程在栈帧中创建Lock Record,CAS将对象头Mark Word替换为指向Lock Record的指针。自旋等待(自适应自旋)。

重量级锁(10)

自旋等待仍失败或自旋中被抢占,锁膨胀为重量级锁。未获取锁的线程阻塞(OS Mutex),进入等待队列,不消耗CPU。

4.3 锁升级流程

  1. 初始无锁状态
  2. 第一个线程访问 → 偏向锁(CAS设置Thread ID),之后每次直接检查ID
  3. 第二个线程访问(轻微竞争)→ 撤销偏向锁 → 升级为轻量级锁,各线程自旋抢锁
  4. 自旋失败或竞争激烈 → 升级为重量级锁,未抢到的阻塞

4.4 其他优化

技术

说明

锁消除

JIT编译时分析,无逃逸的同步代码去掉锁(如StringBuffer在局部变量中使用)

锁粗化

连续对同一对象加锁/解锁合并为一次范围更大的锁(如循环内synchronized→锁加在循环外)

自适应自旋

自旋时间由前一次同一锁的自旋结果决定:上次自旋成功时间长则延长自旋,上次失败则缩短或直接阻塞


五、AQS(AbstractQueuedSynchronizer)

AQS是JUC最核心的框架,ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock等均基于AQS实现。

5.1 核心组成

  • state:volatile int,同步状态(独占模式下0=未锁,1=已锁;共享模式表示剩余资源数)
  • CLH队列变体:双向链表(FIFO),存储等待线程的Node。head是哑节点(已获取锁),tail指向队尾
  • CAS:修改state和入队操作都用CAS,保证无锁竞争下的高效
  • LockSupport.park/unpark:节点前驱是head且抢锁失败后park挂起;释放锁后unpark后继

5.2 独占模式流程(以ReentrantLock为例)

  1. lock():先尝试直接CAS修改state 0→1(非公平锁特有)→ 成功则设置owner=当前线程
  2. acquire(1):CAS失败进入acquire → tryAcquire(子类重写,如ReentrantLock会检查重入) → 失败则addWaiter(Node.EXCLUSIVE)入队CLH → acquireQueued:循环尝试,前驱是head时tryAcquire抢锁 → 失败则shouldParkAfterFailedAcquire检查前驱信号 → 前驱SIGNAL则parkAndCheckInterrupt挂起
  3. unlock():release(1) → tryRelease(state-1,若state=0则清空owner) → unparkSuccessor唤醒后继节点

5.3 共享模式流程(以Semaphore为例)

  • acquireShared:tryAcquireShared(返回剩余资源数) → 失败入队 → 前驱为head时tryAcquireShared→ 失败park
  • releaseShared:tryReleaseShared(state递增) → 成功后doReleaseShared唤醒后继 → 共享传播:唤醒后传递唤醒信号(setHeadAndPropagate),形成链式唤醒

5.4 AQS实现类一览

实现类

模式

state含义

ReentrantLock

独占

0=未锁,≥1=锁次数(重入计数)

ReentrantReadWriteLock

独占+共享

高16位读锁计数,低16位写锁计数

Semaphore

共享

剩余许可数

CountDownLatch

共享

剩余计数(减到0唤醒所有等待线程)

CyclicBarrier

不直接基于AQS(用ReentrantLock+Condition)


六、ReentrantLock

6.1 synchronized vs ReentrantLock

对比

synchronized

ReentrantLock

锁实现

JVM内置,C++实现

JDK实现,AQS框架

锁释放

自动(代码块/方法结束或异常)

手动(必须finally中unlock)

可中断

不可中断

lockInterruptibly()可响应中断

超时获取

不支持

tryLock(timeout, unit) 超时返回false

公平性

非公平(固定)

可选公平/非公平(构造参数)

多条件

单个条件wait/notify

多个Condition精准唤醒

性能

JDK6优化后差异很小

JDK6优化后差异很小

6.2 公平锁 vs 非公平锁

  • 公平锁:lock()直接调用acquire(1)走CLH队列入队。严格FIFO,减少饥饿但吞吐量低(每次唤醒都要切换上下文)
  • 非公平锁(默认):lock()先CAS抢一次,失败再入队。吞吐量高(减少上下文切换),但可能线程饥饿

6.3 Condition 条件变量

📌

synchronized用wait/notify/notifyAll配合一个监视器锁实现等待通知。ReentrantLock通过newCondition()创建多个Condition,精准唤醒特定线程。底层用AQS的ConditionObject,每个Condition维护自己的单向等待队列。

  • await():当前线程加入Condition等待队列 → 完全释放锁(state清空) → park挂起
  • signal():从Condition队列头部移出一个节点到CLH同步队列 → unpark唤醒
  • signalAll():将Condition队列所有节点转移到CLH同步队列

七、线程池

7.1 ThreadPoolExecutor 7个参数

💡

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

参数

说明

corePoolSize

核心线程数(常驻,除非allowCoreThreadTimeOut=true)

maximumPoolSize

最大线程数(达到时执行拒绝策略)

keepAliveTime

非核心线程空闲存活时间

workQueue

阻塞队列(存放等待执行的任务)

threadFactory

线程工厂(命名线程,便于排查)

handler

拒绝策略

7.2 任务执行流程

  1. 提交任务 → 当前线程数 < corePoolSize → 直接创建核心线程执行任务(即使有空闲核心线程也先创建)
  2. 核心线程数已达上限 → 任务入队workQueue等待
  3. 队列已满 && 线程数 < maximumPoolSize → 创建非核心线程执行
  4. 线程数已达maximumPoolSize → 执行拒绝策略

7.3 4种拒绝策略

策略

行为

AbortPolicy(默认)

抛RejectedExecutionException,中断任务提交

CallerRunsPolicy

由提交任务的线程自己执行(自然形成背压,提交速率降低)

DiscardPolicy

直接丢弃任务,不抛异常(危险)

DiscardOldestPolicy

丢弃队列中最老的任务,重新提交当前任务

7.4 Executors 四种快捷线程池(均不推荐生产使用)

线程池

队列

风险

newFixedThreadPool(n)

LinkedBlockingQueue(无界)

队列无限堆积 → OOM

newSingleThreadExecutor()

LinkedBlockingQueue(无界)

同上,队列无限堆积 → OOM

newCachedThreadPool()

SynchronousQueue(0容量)

线程数无限增长(Integer.MAX_VALUE) → OOM

newScheduledThreadPool(n)

DelayedWorkQueue(无界)

队列无限堆积 → OOM

阿里巴巴规范:必须通过ThreadPoolExecutor构造函数显式指定参数,禁止使用Executors工厂方法。

7.5 线程池大小设置

  • CPU密集型:核心线程数 = CPU核数 + 1。Runtime.getRuntime().availableProcessors()
  • IO密集型:核心线程数 = CPU核数 × 2 或 CPU核数 ÷ (1 - 阻塞系数)。阻塞系数通常0.8~0.9
  • 混合型:分开用两个线程池处理

7.6 线程池状态

  • RUNNING → SHUTDOWN(shutdown()) → STOP(shutdownNow()) → TIDYING → TERMINATED
  • SHUTDOWN不接收新任务但执行队列中的;STOP不接收新任务且中断正在执行的

八、原子类(Atomic系列)

8.1 分类

类别

实现类

基本类型

AtomicInteger、AtomicLong、AtomicBoolean

数组类型

AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

引用类型

AtomicReference、AtomicStampedReference(带版本号解决ABA)、AtomicMarkableReference(布尔标记)

属性更新类型

AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater(反射更新volatile字段,节省内存)

累加器(JDK8+)

LongAdder、DoubleAdder(高并发下优于AtomicLong);LongAccumulator、DoubleAccumulator(自定义运算)

8.2 LongAdder 原理

📌

AtomicLong在高并发下CAS频繁失败自旋,性能下降。LongAdder将热点分散到Cell数组:base + sum(Cell[]),每个线程CAS自己的Cell,减少竞争。最终sum()汇总base和所有Cell的值。

  • 适用:高并发累加统计场景(计数器、QPS统计)
  • 不适用:需要精确瞬时值的场景(sum()非原子操作)

九、同步工具类

9.1 CountDownLatch(倒计时门闩)

  • 作用:一个线程等待N个线程完成后再执行。初始化设定计数,countDown()减1,await()阻塞直到计数归零
  • 一次性:计数归零后不可重置
  • 底层:AQS共享模式,state=计数。countDown→releaseShared;await→acquireSharedInterruptibly

9.2 CyclicBarrier(循环栅栏)

  • 作用:N个线程相互等待,全部到达屏障后一起继续执行。可循环使用
  • 回调:构造可传入barrierAction,最后一个到达的线程会执行此回调
  • 底层:ReentrantLock + Condition(非直接基于AQS)。broken=true时重置

9.3 Semaphore(信号量)

  • 作用:限制同时访问某资源的线程数量(限流)。acquire()获取许可,release()归还许可
  • 公平/非公平:公平模式严格FIFO;非公平模式CAS抢许可
  • 底层:AQS共享模式,state=许可数

9.4 三者对比

对比

CountDownLatch

CyclicBarrier

核心机制

一个等多个(或互相等)

多方互相等待

可重用

否(计数归零就废了)

可(计数归零自动重置)

回调

可传Runnable回调

底层

AQS共享

ReentrantLock + Condition

9.5 Exchanger

  • 两个线程间交换数据。exchange(V data):线程A到达时若线程B已到则交换数据返回;否则A自旋等待B到达

9.6 Phaser(JDK7+)

  • 增强版CyclicBarrier + CountDownLatch。支持多阶段同步、动态增减参与者、非阻塞arrive

十、ThreadLocal

10.1 原理

📌

每个Thread内部维护一个ThreadLocalMap,key为ThreadLocal的弱引用,value为线程本地变量副本。get/set操作都是操作当前线程的ThreadLocalMap。

10.2 内存泄漏问题

  • 原因:ThreadLocal为key的弱引用被GC回收后变成null,但value强引用仍在ThreadLocalMap中(Entry的key为null但value不为null),线程长期存活(线程池)逐渐堆积无法回收
  • JDK8优化:get/set/remove时顺带清理key=null的Entry(expungeStaleEntry),但如果不主动调用这仨方法仍会泄漏
  • 最佳实践:用完必须调remove()。线程池场景下调用方在finally中remove

10.3 InheritableThreadLocal

  • 子线程可继承父线程的ThreadLocal值(Thread构造时copy InheritableThreadLocal值到子线程)
  • 局限性:线程池场景下线程复用导致值不更新。替代:TransmittableThreadLocal(alibaba TTL,支持线程池传递)

十一、CompletableFuture(JDK8+)

💡

异步编程利器。相比Future,CompletableFuture支持回调、链式组合、异常处理、多任务编排,强大得多。

11.1 创建方式

方法

说明

supplyAsync(Supplier)

有返回值异步任务(默认ForkJoinPool,可传自定义线程池)

runAsync(Runnable)

无返回值异步任务

completedFuture(value)

创建已完成的Future

11.2 任务编排

方法

作用

thenApply

依赖前一步结果转换(同步,同线程)

thenApplyAsync

同上,异步线程

thenAccept

消费前一步结果,无返回(Consumer)

thenRun

前一步完成后执行,不关心结果

thenCompose

扁平化组合(连接两个依赖的异步任务,避免Future嵌套)

thenCombine

合并两个独立任务结果(BiFunction)

thenAcceptBoth

消费两个任务结果无返回

applyToEither

两个任务谁快用谁的结果

allOf

等待所有任务完成(返回Void)

anyOf

任一任务完成即返回

11.3 异常处理

  • exceptionally:相当于catch,发生异常时提供默认值
  • handle:相当于finally(无论是否异常都执行,有result和exception两个参数)
  • whenComplete:消费结果+异常,不修改返回值

十二、Fork/Join 框架

12.1 核心思想

  • 分治:大任务递归拆分为小任务 → 并行执行 → 合并结果
  • 工作窃取(Work Stealing):空闲线程从繁忙线程的双端队列尾部窃取任务执行。ForkJoinPool每个线程维护一个WorkQueue

12.2 使用方式

  • 继承RecursiveTask<V>(有返回值)或 RecursiveAction(无返回值),重写compute()
  • ForkJoinPool.commonPool()(JDK8默认并行流线程池,核数-1个线程)

面试高频TOP10:

  1. synchronized 锁升级过程(偏向→轻量→重量)
  2. AQS原理(state + CLH队列 + CAS)
  3. ReentrantLock vs synchronized 对比
  4. 线程池7参数和执行流程
  5. volatile 三大特性 + DCL单例
  6. CAS原理 + ABA问题
  7. ThreadLocal 内存泄漏
  8. CountDownLatch vs CyclicBarrier 对比
  9. LongAdder 原理(分散热点)
  10. CompletableFuture 异步编排