在Java并发编程中,锁是解决线程安全、保证共享变量原子性、可见性、有序性的核心手段。

很多开发者只会简单使用 synchronized 和 ReentrantLock,但对锁的底层原理、锁升级、自旋锁、偏向锁、读写锁等核心概念一知半解。遇到并发脏数据、锁等待、死锁、性能瓶颈时无从下手。

本文将从基础概念 → 锁分类 → 底层原理 → 锁升级机制 → 常用锁详解 → 场景对比,系统性梳理Java全套锁机制,看完彻底搞定Java并发锁,适配日常开发、线上调优、面试高频考点。

一、为什么需要锁?并发三大问题

多线程同时操作共享资源时,会出现三大线程安全问题,锁的作用就是彻底解决这三类问题:

  • 原子性问题:一个操作要么全部执行成功,要么全部不执行,防止指令交错

  • 可见性问题:一个线程修改共享变量,其他线程无法及时感知

  • 有序性问题:CPU指令重排序导致业务逻辑执行顺序错乱

锁的核心本质:让并发操作变为串行执行,保证共享资源操作的安全性,代价是牺牲部分并发性能。

二、Java锁整体分类(建立全局认知)

Java锁体系庞大,我们通过维度快速区分,避免概念混乱:

1. 底层实现维度

  • 内置锁(悲观锁):synchronized,JVM底层实现,无需手动加锁解锁

  • 显式锁(API锁):Lock 接口实现类,如 ReentrantLock,Java代码层面实现,灵活性更高

2. 竞争机制维度

  • 悲观锁:默认并发一定会产生竞争,操作资源前直接加锁,阻塞其他线程(synchronized、ReentrantLock)

  • 乐观锁:默认无竞争,不加锁,通过版本号/ CAS 重试解决冲突(CAS、Atomic原子类)

3. 状态升级维度(JDK1.6重点优化)

synchronized 不再是重量级锁,存在锁升级机制:无锁 → 偏向锁 → 轻量级锁(自旋锁) → 重量级锁

4. 功能特性维度

  • 可重入锁:同一线程可多次获取同一把锁,避免死锁(synchronized、ReentrantLock)

  • 读写锁:读多写少场景,读写分离,提升并发性能(ReentrantReadWriteLock)

  • 公平/非公平锁:公平锁线程排队执行,非公平锁允许抢占,吞吐量更高

  • 分段锁:细化锁粒度,提升并发(JDK7 ConcurrentHashMap)

三、核心底层原理:CAS 与 AQS(所有显式锁的基石)

想要读懂Java锁,必须先吃透 CAS 和 AQS,这是JUC锁机制的底层核心。

1. CAS 无锁乐观机制

CAS(Compare And Swap,比较并交换)是乐观锁的底层实现,是一种无锁、非阻塞的原子操作。

执行逻辑:内存值V、旧预期值A、新值B,仅当 V == A 时,将V更新为B,否则重试。

优点:无线程阻塞、无锁竞争开销、并发性能极高

缺点:自旋空转消耗CPU、存在ABA问题、只能保证单个变量原子性

应用场景:Atomic系列原子类、轻量级锁、自旋锁

2. AQS 抽象队列同步器

AQS(AbstractQueuedSynchronizer) 是Java所有显式锁的底层模板,ReentrantLock、读写锁、CountDownLatch、Semaphore 全部基于 AQS 实现。

AQS核心三要素:

  • state状态变量:int类型,标识锁状态,0=无锁、1=加锁、>1=可重入次数

  • 双向阻塞队列:竞争失败的线程进入队列排队,唤醒机制由JVM完成

  • 独占/共享模式:独占锁(ReentrantLock)、共享锁(读写锁、信号量)

简单理解:AQS 是锁的骨架,各种锁只是修改了加锁、解锁的业务逻辑。

四、synchronized 内置锁与锁升级机制(重点)

JDK1.6 之前,synchronized 是笨重的重量级锁,性能极差。JDK1.6 之后引入锁升级优化,性能大幅提升,几乎媲美ReentrantLock。

1. 锁的四种状态(不可逆升级)

锁状态只能升级、不能降级,升级顺序:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

2. 各状态原理与场景

(1)偏向锁:单线程专属锁(无竞争)

核心思想:默认只有一个线程使用锁,无需每次加锁解锁。

线程第一次获取锁后,JVM记录线程ID,后续该线程再次加锁,直接判定归属,无需任何竞争操作,性能极高。

适用场景:单线程重复获取锁、无多线程竞争

(2)轻量级锁:少量竞争,自旋重试

当出现多线程交替竞争锁,无并行竞争时,偏向锁升级为轻量级锁。

核心思想:不阻塞线程,通过CAS自旋重试获取锁,避免线程阻塞、唤醒的内核态开销。

适用场景:线程交替执行、竞争不激烈、锁持有时间短

(3)重量级锁:高并发竞争,线程阻塞

当自旋重试超过次数、或多个线程同时竞争锁,轻量级锁升级为重量级锁。

核心思想:竞争失败的线程进入阻塞队列,挂起等待唤醒。依赖操作系统mutex锁,线程会从用户态切换内核态,开销大、性能低。

适用场景:高并发、锁持有时间长、竞争激烈

3. synchronized 使用场景

  • 修饰普通方法:锁当前对象this

  • 修饰静态方法:锁当前Class类对象(全局锁)

  • 修饰代码块:锁自定义对象,锁粒度更细,性能更好

五、ReentrantLock 显式可重入锁(AQS核心实现)

ReentrantLock 是 Lock 接口最常用的实现,功能比 synchronized 更强大、更灵活,高并发场景优先使用。

1. 核心特性

  • 可重入:同一线程可多次加锁,计数累加,解锁递减,彻底避免自身死锁

  • 可公平/非公平:默认非公平锁(吞吐量高),可手动开启公平锁(线程有序排队)

  • 可中断锁:线程等待锁时可响应中断,避免死等

  • 超时锁:支持tryLock超时获取,避免永久阻塞

  • 条件唤醒:通过Condition精准唤醒指定线程,替代wait/notify粗放唤醒

2. 底层原理

基于AQS实现,通过state变量记录锁状态:

  • state=0:无锁状态

  • state>0:已加锁,数值代表可重入次数

  • 竞争失败线程进入AQS双向队列阻塞排队

六、ReentrantReadWriteLock 读写锁(读多写少神器)

普通排他锁会导致:读操作之间也互斥,但读操作是线程安全的,无需互斥,严重浪费并发性能。

读写锁专门解决读多写少场景,实现读读共享、读写互斥、写写互斥。

1. 核心规则

  • 多个线程同时读:共享锁,并行执行,无阻塞

  • 读+写:互斥,写操作阻塞读,读阻塞写

  • 写+写:互斥,串行执行

2. 适用场景

缓存数据、配置信息、静态资源、商品详情等查询远超修改的业务场景,并发吞吐量可提升数倍。

七、乐观锁 CAS & 原子类

悲观锁会阻塞线程、牺牲并发,而CAS乐观锁全程无阻塞、无线程挂起,适合竞争极少、变量更新简单的场景。

1. 核心优势

无锁开销、无线程切换、并发性能碾压悲观锁

2. 存在问题

  • ABA问题:变量从A→B→A,CAS无法感知数据变动,可通过版本号解决

  • CPU空转:竞争激烈时,CAS无限自旋,消耗CPU资源

  • 只能保证单个变量原子性:无法保证多变量复合操作原子性

3. 应用

AtomicInteger、AtomicLong、LongAdder 等原子工具类,底层全部基于CAS实现。

八、synchronized 与 ReentrantLock 终极对比

对比维度

synchronized

ReentrantLock

实现层级

JVM内置、底层原生

JDK代码层面、AQS实现

锁特性

可重入、非公平、不可中断

可重入、公平/非公平、可中断、超时锁

唤醒机制

随机唤醒、粗放式

Condition精准唤醒

性能

JDK1.6后优化极强,低竞争性能优秀

高竞争场景性能更稳定

使用场景

简单同步、代码块、方法锁

高并发、复杂锁逻辑、限时等待、精准唤醒

九、开发锁选型最佳实践

  1. 简单同步场景:优先使用 synchronized,简洁优雅、无需手动解锁、不易出错

  2. 高并发、复杂锁场景:使用 ReentrantLock,灵活可控,支持超时、中断、精准唤醒

  3. 读多写少场景:必用 ReentrantReadWriteLock,最大化并发吞吐量

  4. 单变量计数、简单更新:使用 Atomic 原子类,CAS无锁高性能

  5. 超高并发计数:使用 LongAdder,分段CAS,减少竞争

十、常见锁问题避坑

  • 死锁:多线程互相持有对方锁、等待对方资源,解决:统一加锁顺序、超时锁、避免嵌套锁

  • 锁粒度太大:锁整个方法/对象,并发串行化,优化:缩小锁范围,只锁核心代码

  • 锁失效:锁对象变更、String常量锁、基本类型锁导致锁失效

  • 长锁阻塞:锁内执行IO、耗时逻辑,导致大量线程阻塞

总结

Java锁机制的核心逻辑可以概括为一句话:以CAS、AQS为底层基石,以锁升级、读写分离为性能优化手段,根据竞争场景匹配不同锁模型。

从最基础的内置锁 synchronized,到灵活的显式锁 ReentrantLock,再到高性能读写锁、乐观CAS锁,层层递进,覆盖了Java所有并发同步场景。

日常开发不用盲目用高级锁,合适的场景选合适的锁,才是并发编程的最优解。