在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,灵活可控,支持超时、中断、精准唤醒
读多写少场景:必用 ReentrantReadWriteLock,最大化并发吞吐量
单变量计数、简单更新:使用 Atomic 原子类,CAS无锁高性能
超高并发计数:使用 LongAdder,分段CAS,减少竞争
十、常见锁问题避坑
死锁:多线程互相持有对方锁、等待对方资源,解决:统一加锁顺序、超时锁、避免嵌套锁
锁粒度太大:锁整个方法/对象,并发串行化,优化:缩小锁范围,只锁核心代码
锁失效:锁对象变更、String常量锁、基本类型锁导致锁失效
长锁阻塞:锁内执行IO、耗时逻辑,导致大量线程阻塞
总结
Java锁机制的核心逻辑可以概括为一句话:以CAS、AQS为底层基石,以锁升级、读写分离为性能优化手段,根据竞争场景匹配不同锁模型。
从最基础的内置锁 synchronized,到灵活的显式锁 ReentrantLock,再到高性能读写锁、乐观CAS锁,层层递进,覆盖了Java所有并发同步场景。
日常开发不用盲目用高级锁,合适的场景选合适的锁,才是并发编程的最优解。
评论