当前位置: 首页
业界动态
Java锁机制从同步锁到重入锁深度解析与实战对比

Java锁机制从同步锁到重入锁深度解析与实战对比

热心网友 时间:2026-06-29
转载

有一个朋友跳槽到了新公司,接手的首个任务就是排查线上死锁问题。两个接口相互调用,偶尔会出现卡死现象,且只在流量高峰期爆发。他用jstack查看后发现,两个线程都在等待对方释放锁,这是典型的死锁场景。

但真正引发思考的是,他提到一个细节:其中一个接口使用了synchronized,另一个则用了ReentrantLock。这不禁让人追问——既然两种锁都能实现互斥,为什么Java要设计两套锁机制?它们的底层实现究竟有何差异?在什么场景下应该选择哪一个?

深入源码分析后发现,这个问题远比表面复杂。synchronized从JVM的对象头Mark Word到锁升级机制,ReentrantLock从AQS的state变量到CLH队列,每层设计都蕴含精妙之处。更关键的是,理解这些原理才能真正掌握:为什么有些场景用synchronized性能更优,而另一些场景则必须依赖ReentrantLock

这篇文章将带你深入Java锁机制的核心原理,通过对比分析、源码解析和实战案例,帮助你构建完整的知识体系。

二、synchronized深度解析

2.1 synchronized的实现原理

synchronized是Java语言内置的锁机制,称它为"亲儿子"并不过分。它的实现完全依赖JVM,核心在于对象头和Monitor机制。

对象头结构

每个Java对象在JVM中都有一个对象头,其中的Mark Word部分存储了锁状态信息:

图片图片

图片

Monitor机制

synchronized修饰方法或代码块时,JVM会通过Monitor来实现互斥:

public class SynchronizedDemo { // 方法级锁 public synchronized void method() { // 业务逻辑 } // 代码块锁 public void block() { synchronized(this) { // 业务逻辑 } }}

在字节码层面:

方法级锁:通过ACC_SYNCHRONIZED标志隐式实现

代码块锁:通过monitorentermonitorexit指令显式实现

synchronized锁升级流程图:

图片图片

上图展示了synchronized从无锁到重量级锁的完整升级路径:

无锁状态:对象刚创建,没有任何线程访问

偏向锁:第一个线程访问时,在对象头记录线程ID

轻量级锁:多个线程交替访问时,使用CAS自旋

重量级锁:竞争激烈时,使用Monitor阻塞等待

2.2 锁升级详细过程

偏向锁

偏向锁的假设很有意思:它认为锁主要由同一个线程多次获得。于是通过在对象头中记录线程ID来避免同步,类似于给锁贴上了"专属标签":

// 偏向锁开启参数-XX:+UseBiasedLocking-XX:BiasedLockingStartupDelay=0

偏向锁获取流程:

检查Mark Word是否为可偏向状态(偏向锁标志=1,锁标志=01)

如果是,检查线程ID是否为当前线程

如果是,直接进入同步块

如果不是,通过CAS尝试将线程ID设置为当前线程ID

轻量级锁

当有其他线程来"抢"偏向锁时,就会升级为轻量级锁:

// 轻量级锁获取过程1. 在当前线程栈帧中创建Lock Record2. 将对象头的Mark Word复制到Lock Record3. 用CAS尝试将对象头指向Lock Record4. 如果成功,获得轻量级锁5. 如果失败,说明有竞争,膨胀为重量级锁

轻量级锁使用自旋锁,旨在避免线程切换开销:

默认自旋次数:10次

JDK 6后引入自适应自旋:根据历史自旋成功率动态调整

重量级锁

轻量级锁自旋失败后,会升级为重量级锁:

// ObjectMonitor核心结构class ObjectMonitor { ObjectMonitor() { _header = NULL; _count = 0; // 锁计数器 _waiters = 0, _recursions = 0; // 重入次数 _owner = NULL; // 持有锁的线程 _WaitSet = NULL; // 等待队列 _EntryList = NULL; // 阻塞队列 }}

重量级锁基于操作系统的Mutex Lock实现:

线程获取锁失败后会被阻塞

线程切换需要从用户态切换到内核态

性能开销较大,但能保证公平性

2.3 synchronized实战案例

案例1:单例模式双重检查

public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); } } } return instance; }}

为什么要双重检查?

第一个if:避免不必要的锁竞争

synchronized:保证线程安全

第二个if:防止重复创建

volatile:禁止指令重排序

案例2:线程安全的计数器

public class SynchronizedCounter { private int count = 0; public synchronized void increment() { count++; } public synchronized void decrement() { count--; } public synchronized int getCount() { return count; }}

synchronized保证了:

原子性:count++操作的原子性

可见性:修改后立即刷新到主内存

有序性:防止指令重排序

三、ReentrantLock核心原理

3.1 ReentrantLock基本使用

ReentrantLock是JDK提供的可重入锁,基于AQS(AbstractQueuedSynchronizer)实现,使用起来比synchronized更灵活,但相应地,也需要开发者自行管理锁的获取和释放:

public class ReentrantLockDemo { private final ReentrantLock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } // 可中断锁 public void incrementWithInterrupt() throws InterruptedException { lock.lockInterruptibly(); try { count++; } finally { lock.unlock(); } } // 尝试获取锁 public boolean tryIncrement() { if (lock.tryLock()) { try { count++; return true; } finally { lock.unlock(); } } return false; }}

3.2 AQS核心原理

AQS是ReentrantLock的核心,它使用一个volatile int state字段和一个CLH队列实现:

public abstract class AbstractQueuedSynchronizer { // 同步状态 private volatile int state; // 等待队列头节点 private transient volatile Node head; // 等待队列尾节点 private transient volatile Node tail; // 队列节点 static final class Node { volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 volatile Thread thread; // 等待线程 volatile int waitStatus; // 等待状态 }}

公平锁vs非公平锁

// 非公平锁(默认)public ReentrantLock() { sync = new NonfairSync();}// 公平锁public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}

公平锁与非公平锁对比:

非公平锁:新线程直接尝试CAS,不检查队列

公平锁:新线程检查队列中是否有等待线程,有则排队

3.3 Condition实现原理

Condition提供了类似wait/notify的功能,但更加强大,因为它允许多个条件队列,实现更精细的线程协作:

public class ConditionDemo { private final ReentrantLock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); private final Object[] items = new Object[10]; private int putIndex, takeIndex, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) { notFull.await(); // 队列满,等待 } items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); // 唤醒消费者 } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) { notEmpty.await(); // 队列空,等待 } Object x = items[takeIndex]; if (++takeIndex == items.length) takeIndex = 0; count--; notFull.signal(); // 唤醒生产者 return x; } finally { lock.unlock(); } }}

四、CAS机制详解

4.1 CAS基本概念

CAS(Compare And Swap)是乐观锁的核心实现,其思想是"先尝试,失败再重试",从而避免线程阻塞:

public class CASDemo { private AtomicInteger count = new AtomicInteger(0); public void increment() { int oldValue, newValue; do { oldValue = count.get(); newValue = oldValue + 1; } while (!count.compareAndSet(oldValue, newValue)); }}

CAS操作包含三个操作数:

V:内存值

E:预期值

N:新值

只有当V==E时,才会将V设置为N。

CAS操作原理图:

图片图片

上图展示了CAS的完整执行流程:

读取内存值:从主内存读取当前值

计算新值:根据业务逻辑计算新值

CAS操作:比较内存值与预期值

成功/失败:成功则更新,失败则重试

4.2 ABA问题与解决方案

ABA问题是CAS的经典问题,也是面试中常被问到的陷阱之一:

public class ABAProblem { private static AtomicInteger atomicInt = new AtomicInteger(100); public static void main(String[] args) { // 线程1 new Thread(() -> { atomicInt.compareAndSet(100, 101); atomicInt.compareAndSet(101, 100); }).start(); // 线程2 new Thread(() -> { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } boolean success = atomicInt.compareAndSet(100, 101); System.out.println("CAS成功: " + success); // true }).start(); }}

线程2无法感知值被修改过,这就是ABA问题。

解决方案:AtomicStampedReference

public class ABASolution { private static AtomicStampedReference atomicRef = new AtomicStampedReference<>(100, 0); public static void main(String[] args) { // 线程1 new Thread(() -> { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } atomicRef.compareAndSet(100, 101, atomicRef.getStamp(), atomicRef.getStamp() + 1); atomicRef.compareAndSet(101, 100, atomicRef.getStamp(), atomicRef.getStamp() + 1); }).start(); // 线程2 new Thread(() -> { int stamp = atomicRef.getStamp(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } boolean success = atomicRef.compareAndSet(100, 101, stamp, stamp + 1); System.out.println("CAS成功: " + success); // false }).start(); }}

AtomicStampedReference通过引入版本号机制,每次修改都附带一个"印记",从根本上解决了ABA问题。

五、锁优化策略

JVM在锁优化方面下了不少功夫,其中有几个策略非常关键,理解它们对编写高性能并发代码很有帮助。

5.1 锁消除

JVM通过逃逸分析,能够识别出那些不会被外部访问的对象,从而消除它们上面的锁:

public class LockElimination { public String concat(String s1, String s2) { // StringBuffer是线程安全的,但在这个方法中 // sb不会逃逸,所以JVM会消除锁 StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb.toString(); }}

5.2 锁粗化

JVM会将相邻的锁操作合并,以减少频繁加锁和解锁的开销:

public class LockCoarsening { private final Object lock = new Object(); public void method() { // JVM会将这3个锁合并为1个锁 synchronized(lock) { // 操作1 } synchronized(lock) { // 操作2 } synchronized(lock) { // 操作3 } }}

5.3 锁分离

读写分离,将一把锁拆成读锁和写锁,读读不互斥,大幅提升并发性能:

public class ReadWriteLockDemo { private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Lock readLock = rwLock.readLock(); private final Lock writeLock = rwLock.writeLock(); private Map cache = new HashMap<>(); public Object get(String key) { readLock.lock(); try { return cache.get(key); } finally { readLock.unlock(); } } public void put(String key, Object value) { writeLock.lock(); try { cache.put(key, value); } finally { writeLock.unlock(); } }}

六、synchronized vs ReentrantLock对比

6.1 功能对比

6.2 性能对比

public class LockPerformanceTest { private static final int THREAD_COUNT = 16; private static final int OPERATIONS = 1000000; // synchronized测试 private static int synchronizedCount = 0; private static final Object lock = new Object(); public static void synchronizedIncrement() { synchronized(lock) { synchronizedCount++; } } // ReentrantLock测试 private static int reentrantLockCount = 0; private static final ReentrantLock reentrantLock = new ReentrantLock(); public static void reentrantLockIncrement() { reentrantLock.lock(); try { reentrantLockCount++; } finally { reentrantLock.unlock(); } }}

测试结果(operations/ms):

结论:JDK 6之后,synchronized经过了全方位优化,性能已经和ReentrantLock非常接近。因此,不必再纠结于性能差异,关键是看场景。

6.3 选择建议

使用synchronized的场景:

简单的同步需求

不需要高级特性(中断、超时等)

代码可读性优先

已经有成熟的JVM优化

使用ReentrantLock的场景:

需要公平锁

需要中断响应

需要超时获取锁

需要多个条件变量

需要精细的锁控制

七、实战踩坑指南

理论和源码看完了,但真正写代码时,一些细节上的坑很容易让人"翻车"。下面这几个问题,遇到时都非常令人头疼。

7.1 坑1:锁对象选择错误

// ❌ 错误示例public class BadLock { private Integer count = 0; public void increment() { synchronized(count) { // Integer缓存,可能锁同一个对象 count++; } }}// ✅ 正确示例public class GoodLock { private final Object lock = new Object(); private Integer count = 0; public void increment() { synchronized(lock) { count++; } }}

7.2 坑2:锁粒度过大

// ❌ 错误示例public class BadGranularity { private final Map userMap = new HashMap<>(); public synchronized User getUser(String userId) { return userMap.get(userId); } public synchronized void addUser(String userId, User user) { userMap.put(userId, user); }}// ✅ 正确示例public class GoodGranularity { private final Map userMap = new ConcurrentHashMap<>(); public User getUser(String userId) { return userMap.get(userId); // 读操作无需锁 } public void addUser(String userId, User user) { userMap.put(userId, user); // ConcurrentHashMap内部有锁 }}

7.3 坑3:死锁问题

// ❌ 死锁示例public class DeadlockDemo { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void method1() { synchronized(lock1) { synchronized(lock2) { // 业务逻辑 } } } public void method2() { synchronized(lock2) { synchronized(lock1) { // 业务逻辑 } } }}// ✅ 避免死锁:统一锁顺序public class NoDeadlockDemo { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void method1() { synchronized(lock1) { synchronized(lock2) { // 业务逻辑 } } } public void method2() { synchronized(lock1) { // 统一先获取lock1 synchronized(lock2) { // 再获取lock2 // 业务逻辑 } } }}

7.4 坑4:忘记释放锁

// ❌ 错误示例public class BadRelease { private final ReentrantLock lock = new ReentrantLock(); public void method() { lock.lock(); // 业务逻辑,可能抛异常 lock.unlock(); // 异常时不会执行 }}// ✅ 正确示例public class GoodRelease { private final ReentrantLock lock = new ReentrantLock(); public void method() { lock.lock(); try { // 业务逻辑 } finally { lock.unlock(); // 始终在finally中释放 } }}

八、总结

8.1 核心知识点

通过这篇文章,我们深入理解了Java锁机制的核心原理:

1. synchronized实现机制

对象头Mark Word存储锁状态

锁升级:偏向锁→轻量级锁→重量级锁

JVM层面的Monitor机制

2. ReentrantLock核心原理

基于AQS的CLH队列实现

支持公平锁和非公平锁

提供中断、超时、条件变量等高级特性

3. CAS机制

乐观锁的核心实现

ABA问题及解决方案

AtomicStampedReference版本号机制

4. 锁优化策略

锁消除:逃逸分析

锁粗化:合并相邻锁

锁分离:读写分离

8.2 最佳实践

优先使用synchronized:简单场景,JVM优化好

高级特性用ReentrantLock:公平锁、中断、超时

锁对象要稳定:使用final Object

锁粒度要适中:避免过大或过小

释放锁要在finally:保证锁一定释放

避免死锁:统一锁顺序,使用tryLock

九、面试加分项(Q&A)

Q1:synchronized和ReentrantLock有什么区别?

synchronized和ReentrantLock都能实现线程同步,但有本质区别。synchronized是JVM层面的锁,通过对象头和Monitor实现,支持锁升级优化,使用简单但功能有限。ReentrantLock是JDK API层面的锁,基于AQS实现,提供公平/非公平选择、中断响应、超时获取、多条件变量等高级特性,使用灵活但需要手动释放锁。

性能方面,JDK 6后synchronized经过优化(偏向锁、轻量级锁、自适应自旋),性能已接近ReentrantLock。选择建议:简单场景用synchronized,需要高级特性时用ReentrantLock。

Q2:synchronized的锁升级过程是怎样的?为什么要这样设计?

synchronized锁升级过程:无锁→偏向锁→轻量级锁→重量级锁。偏向锁假设锁主要由同一线程获得,在对象头记录线程ID避免同步;轻量级锁在多线程交替访问时使用CAS自旋,避免阻塞;重量级锁在竞争激烈时使用Monitor阻塞等待。

这样设计是为了平衡性能和公平性。偏向锁和轻量级锁在无竞争或弱竞争时性能高,避免操作系统级别的阻塞;重量级锁在强竞争时保证公平性,避免CPU空转。这种自适应策略让synchronized在大多数场景下性能优异。

Q3:什么是ABA问题?如何解决?

ABA问题是CAS的经典陷阱。线程1读取变量值为A,准备CAS修改为C;此时线程2将A改为B,又改回A;线程1执行CAS时,发现值还是A,修改成功。但实际上值已经被修改过两次,可能导致业务逻辑错误。

解决方案有三种:1)AtomicStampedReference使用版本号机制,每次修改版本号+1,CAS时同时检查值和版本号;2)AtomicMarkableReference使用布尔标记,适用于只需判断是否修改过的场景;3)使用互斥锁替代CAS,彻底避免ABA问题。实际项目中推荐第一种方案。

Q4:公平锁和非公平锁有什么区别?如何选择?

公平锁严格按照FIFO顺序获取锁,新线程必须排队等待;非公平锁允许新线程插队,直接尝试CAS获取锁,失败后才排队。公平锁保证公平,避免线程饥饿,但需要维护队列,性能较低;非公平锁性能高,减少上下文切换,但可能导致某些线程长时间获取不到锁。

选择建议:1)默认用非公平锁,性能更好;2)对公平性要求高的场景(如资源分配、任务调度)用公平锁;3)ReentrantLock默认是非公平锁,可通过构造函数指定fair=true创建公平锁。实际项目中,非公平锁能满足大多数场景。

Q5:如何避免死锁?死锁的四个必要条件是什么?

死锁的四个必要条件:1)互斥条件:资源一次只能被一个线程占用;2)占有并等待:线程持有资源同时等待其他资源;3)不可剥夺:资源不能强制抢占;4)循环等待:线程间形成循环等待关系。四个条件同时满足才会死锁。

避免死锁的策略:1)统一锁顺序:所有线程按相同顺序获取锁;2)使用tryLock超时获取:获取不到锁时释放已持有的锁;3)缩小锁范围:减少持锁时间;4)使用Lock的lockInterruptibly():响应中断打破死锁;5)使用工具检测:jstack、JConsole、Arthas都能检测死锁。最有效的是统一锁顺序。

参考资源

《Java并发编程实战》

《Java并发编程的艺术》

OpenJDK Wiki: Synchronization

JSR 133: Java Memory Model and Thread Specification

来源:https://www.51cto.com/article/840099.html

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
长安汽车明年一季度发布首款车载人形机器人小安

长安汽车明年一季度发布首款车载人形机器人小安

长安汽车公布机器人战略,采用“1+N+X”布局,联合头部伙伴攻克大脑、能源、驱动技术。人形机器人“小安”身高169cm,体重69kg,移动速度0 8m s,具备40个自由度,续航超2小时。预计明年一季度发布首款车载组件机器人,已在广州车展展示。

时间:2026-06-29 14:02
中国信科刷新光通信世界纪录 每秒可下载1.4万部4K电影

中国信科刷新光通信世界纪录 每秒可下载1.4万部4K电影

3月25日,光通信领域迎来又一个里程碑:中国信科集团光通信技术和网络全国重点实验室联合鹏城实验室、烽火藤仓光纤科技有限公司,成功实现了2 5Pb s 24芯光纤超大容量实时光传输,再次刷新了世界纪录。 这一研究成果不仅入选国际顶级光通信会议OFC(2026)并荣获“高分论文”称号,还受国际权威SCI

时间:2026-06-29 14:02
美国调查18万辆特斯拉Model3车门应急释放装置易找性

美国调查18万辆特斯拉Model3车门应急释放装置易找性

美国国家公路交通安全管理局对约17 9万辆2024款特斯拉Model3启动缺陷调查,焦点在于车门应急释放装置是否不易找到且标识不清。该调查源于一份缺陷请愿,不意味着立即召回,但可能引发后续监管措施。

时间:2026-06-29 14:01
doc个人图书馆停服 创始人称无偿转让失败

doc个人图书馆停服 创始人称无偿转让失败

运营长达20年,累计服务8000万用户的360doc个人图书馆,最终还是迎来了谢幕时刻。2026年5月1日,这个承载着无数用户收藏记忆的知名平台将正式停止服务——关停原因并非用户流失,而是始终未能寻得一位能够安全接管的合适人选。 创始人蔡智在告别信中坦言,近两个月来,他一直在尝试将360doc无偿转

时间:2026-06-29 14:01
年Q1随身WiFi实测安全靠谱高性价比机型推荐

年Q1随身WiFi实测安全靠谱高性价比机型推荐

2025年10月,艾瑞咨询正式授予飞猫“AI WiFi品类开创者”认证,紧接着CIC也将其认定为“多网融合自由切换技术服务首创者”。这些权威认证背后,折射出一个清晰的市场趋势:移动办公、户外出行、宿舍上网等场景的需求正在快速增长,随身WiFi几乎已成为不少用户的刚需装备。但问题也随之而来——网络卡顿

时间:2026-06-29 14:01
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜