1. synchronized 锁的机制和升级过程
偏向锁(Biased Locking)
-
原理:当一个线程第一次请求锁时,JVM 会记录该线程的 ID,并将锁标记为偏向锁。此后,若同一线程再次访问该锁,JVM 会直接认为该线程已经持有锁,从而避免加锁和解锁操作,提升性能。
-
工作流程:
-
线程第一次请求锁时,JVM 将锁对象标记为偏向锁,并记录线程 ID。
-
当相同线程再次访问该锁时,JVM 检查对象头中的线程 ID,若匹配,线程直接获取锁,无需再次加锁。
-
如果其他线程争用锁,偏向锁会被撤销,锁将进入轻量级锁或重量级锁状态。
-
-
适用场景:适用于没有竞争的单线程场景,可以显著提升性能。
轻量级锁(Lightweight Locking)
-
原理:轻量级锁通常是偏向锁升级后的结果。当多个线程争用同一个锁时,偏向锁无法继续使用,JVM 会将其升级为轻量级锁。此时,线程会通过自旋(CAS)来尝试获取锁,而非立即阻塞。
-
工作流程:
-
线程请求锁时,如果当前为偏向锁且无竞争,线程直接获得锁。
-
如果发生竞争,偏向锁升级为轻量级锁,线程通过自旋(CAS)尝试获取锁。
-
如果自旋次数过多,锁将升级为重量级锁,导致线程阻塞。
-
-
适用场景:适用于锁竞争较少的场景,减少了上下文切换的开销。
自旋锁(Spin Lock)
-
原理:线程在尝试获取锁时,如果锁被占用,线程不会立即阻塞,而是会持续尝试获取锁,这就是“自旋”。自旋锁减少了线程上下文切换的开销。
-
工作方式:
-
线程使用 CAS 获取锁,如果获取成功,则持有锁;
-
如果锁被占用,线程会不断自旋(重试),直到获取锁或者达到自旋次数上限。
-
自旋锁是轻量级的,但如果自旋过多,可能浪费 CPU 时间,因此当自旋超过一定次数时,锁会升级为重量级锁。
-
-
适用场景:适用于竞争不激烈的场景,减少了系统资源的消耗。
重量级锁(Heavyweight Locking)
-
原理:当自旋锁尝试多次失败,或线程竞争过于激烈时,JVM 会将轻量级锁升级为重量级锁。此时,线程将被阻塞,操作系统会介入进行调度。
-
适用场景:适用于锁竞争非常激烈的场景。
锁升级过程总结:
-
偏向锁:无竞争时,JVM 会优先使用偏向锁,避免不必要的加锁。
-
轻量级锁:有线程竞争时,偏向锁被升级为轻量级锁,线程通过自旋(CAS)争抢锁。
-
重量级锁:当自旋失败或竞争非常激烈时,轻量级锁升级为重量级锁,线程会被阻塞,等待操作系统调度。
你发的图片内容总结了 synchronized 的实现原理,其核心观点是准确的。下面我根据这张图进行提炼和扩展说明,让你对 synchronized 的底层机制有更完整的理解:
🧩 synchronized 是如何实现线程同步的()
一、作用与基本概念
-
synchronized是 Java 提供的原生同步机制,用于控制多个线程对共享资源的访问。 -
它可以修饰:
-
实例方法(锁的是当前对象
this) -
静态方法(锁的是
Class对象) -
代码块(锁的是指定的对象)
-
✅ 关键点:任意时刻只有一个线程能获取锁,进入同步代码
二、底层原理
🔒 基于 监视器锁(Monitor Lock) 实现
-
每个 Java 对象都隐含地与一个 Monitor(监视器锁) 关联
-
当一个线程执行
synchronized时,会去尝试获取这个对象的 Monitor -
获取成功才可以进入同步块,否则会阻塞等待
🧠 依赖于操作系统的 互斥锁(Mutex) 实现
-
Monitor 在 HotSpot VM 中由
ObjectMonitor实现 -
ObjectMonitor 内部可能调用操作系统的原生锁机制(如互斥量)
三、对象头 + Mark Word(扩展说明)
图中提到:
Java 的对象有一个头(Object Header),其中包含一个 Mark Word,用于存储锁的信息。
这是实现 synchronized 的关键点之一:
📦 Mark Word 中的信息包括:
-
锁标志位(无锁、偏向锁、轻量级、重量级)
-
锁拥有者线程 ID(在加锁状态下)
-
哈希码、GC 分代信息(非加锁时)
🔄 锁状态会自动升级
根据竞争情况,JVM 会进行锁的升级或降级以优化性能:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
-
偏向锁:单线程反复加锁时极高效(无 CAS)
-
轻量级锁:多线程但无竞争,采用 CAS 操作
-
重量级锁:多线程并发竞争严重,线程阻塞
✅ 小结:
| 内容类别 | 说明 |
|---|---|
| 功能目的 | synchronized 保证共享资源在并发访问中不被破坏 |
| 作用范围 | 方法、代码块 |
| 底层实现 | 基于对象的 Monitor(监视器锁) |
| 操作系统基础 | Monitor 内部可能调用 native 的 Mutex |
| 对象结构 | 对象头(含 Mark Word),保存锁信息 |
| 性能优化 | 锁状态根据竞争情况升级:偏向 → 轻量 → 重量 |
synchronized 锁的限制
-
作用范围:
synchronized锁是 JVM 层的锁,作用范围仅限于单个 JVM 实例中的线程间同步。 -
高并发下的失效:
synchronized锁无法跨 JVM 实例(跨服务)进行同步,因此在分布式系统中,synchronized不能用于跨服务锁定,且无法保证在高并发下的有效性。 -
synchronized锁字符串:只能锁住常量池中的字符串,不能锁住new出来的字符串,因为每次new的字符串都是新对象,新资源。
2. ReentrantLock 锁机制
ReentrantLock 的特点
-
可重入性:
ReentrantLock是可重入锁,即同一个线程可以多次获取同一个锁而不会导致死锁。 -
手动加锁和解锁:
ReentrantLock需要通过显式调用lock()方法加锁,通过unlock()方法释放锁,开发者需要注意在finally块中释放锁以防止死锁。 -
公平性选择:
ReentrantLock提供了公平锁和非公平锁两种模式。公平锁保证按请求顺序获取锁,非公平锁则允许线程“插队”,通常非公平锁性能更高。 -
性能:
ReentrantLock在高并发情况下表现优越,能够支持更多的控制操作,如锁中断、定时锁等。
✅ ReentrantLock 的 lock() 与 tryLock() 方法区别笔记
一、两者的基本定义
| 方法 | 作用 | 特点 |
|---|---|---|
lock() | 获取锁,如果锁被其他线程持有,则阻塞等待直到获得锁 | 不可中断(可配合 lockInterruptibly() 支持中断) |
tryLock() | 尝试获取锁,立即返回获取结果,不等待 | 非阻塞,立即得知是否成功 |
二、方法签名对比
// lock(): 阻塞式获取锁
void lock()
// tryLock(): 非阻塞尝试获取锁
boolean tryLock()
// tryLock(long timeout, TimeUnit unit): 限时尝试获取锁
boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException
三、核心区别对比
| 比较项 | lock() | tryLock() |
|---|---|---|
| 是否阻塞 | 是,若获取不到锁会阻塞 | 否,获取不到立即返回 |
| 返回类型 | void(阻塞直到成功) | boolean(立即返回是否成功) |
| 可否响应中断 | 否(使用 lockInterruptibly() 可中断) | 限时版本可响应中断 |
| 使用场景 | 更适用于必须拿到锁的关键逻辑 | 更适用于尝试执行、失败也可以继续其他逻辑的场景 |
| 超时机制 | 不支持超时 | 支持超时(重载版本) |
| 死锁容忍性 | 容易阻塞造成死锁 | 可避免死锁(配合超时或失败处理逻辑) |
四、使用示例
1. lock() 示例
lock.lock();
try {
// 临界区逻辑
} finally {
lock.unlock();
}
2. tryLock() 示例(立即返回)
if (lock.tryLock()) {
try {
// 拿到锁的逻辑
} finally {
lock.unlock();
}
} else {
// 获取失败的逻辑,如日志、降级
}
3. tryLock(long, TimeUnit) 示例(限时等待)
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
// 成功获取锁
} finally {
lock.unlock();
}
} else {
// 超时未获取锁,处理降级逻辑
}
五、适用场景对比
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 非常关键代码,必须持有锁 | lock() | 阻塞直到获取锁,确保逻辑执行 |
| 避免死锁 / 降级处理 | tryLock() | 获取不到锁可以立即返回或降级处理 |
| 高并发非强一致性任务 | tryLock() | 失败快速返回,避免线程长时间等待 |
| 可响应中断的任务调度 | lockInterruptibly() 或 tryLock(timeout) | 能中断等待,增强灵活性 |
六、最佳实践建议
-
不要忘记在
finally中释放锁(防止死锁):lock.lock(); try { // ... } finally { lock.unlock(); } -
使用
tryLock()做降级方案设计,提高系统容错能力; -
lock()可配合lockInterruptibly()使用,以便支持中断响应; -
在高并发系统中,使用
tryLock(timeout)可防止长时间阻塞导致线程资源耗尽。
✅ 总结表
| 方法 | 阻塞 | 可中断 | 是否返回布尔值 | 支持超时 | 使用建议 |
|---|---|---|---|---|---|
lock() | 是 | 否 | 否 | 否 | 保守获取锁,关键逻辑 |
tryLock() | 否 | 否 | 是 | 否 | 快速尝试、非核心逻辑 |
tryLock(timeout) | 限时阻塞 | 是 | 是 | 是 | 限时降级、避免死锁 |
lockInterruptibly() | 是 | 是 | 否 | 否 | 支持线程中断的阻塞锁 |
ReentrantLock 与 synchronized 的区别
-
类型:
-
synchronized是 Java 语言的内置关键字。 -
ReentrantLock是一个实现了Lock接口的类。
-
-
加锁和释放锁:
-
synchronized:由 JVM 自动管理加锁与释放锁。 -
ReentrantLock:由程序员显式调用lock()和unlock()来管理。
-
-
锁的公平性:
-
synchronized:默认是非公平锁。(升级不了公平锁) -
ReentrantLock:可以选择公平锁(通过构造函数参数)或非公平锁。
-
-
死锁检测:
-
synchronized:无法主动检测死锁。 -
ReentrantLock:可以通过tryLock()和lockInterruptibly()方法来避免死锁。
-
-
底层实现:
-
synchronized:JVM 层面的锁,底层通过不同的锁机制(如偏向锁、轻量级锁、重量级锁)进行升级。 -
ReentrantLock:基于 AQS(AbstractQueuedSynchronizer)实现,支持更多灵活的控制。
-
性能比较
-
synchronized:虽然 JVM 在底层优化了锁的升级过程,但由于其无法灵活控制锁行为,性能在高并发情况下不如ReentrantLock。 -
ReentrantLock:提供了更多的灵活性和更好的性能,特别是在高并发和需要更精细锁控制的场景。
3. 锁的跨服务同步问题
-
synchronized锁是 JVM 层面的锁,它无法跨 JVM 实例同步。因此,在 分布式系统 中,synchronized锁无法用于跨服务的线程同步。 -
在分布式系统中,需要使用 分布式锁 来同步不同 JVM 实例中的线程。常见的分布式锁实现有 Redis 分布式锁、Zookeeper 分布式锁等。
总结对比表:synchronized 与 ReentrantLock
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁类型 | JVM 层的关键字 | 类,Lock 接口实现 |
| 加锁和释放锁 | 自动加锁与释放锁 | 手动加锁和释放锁(lock() 和 unlock()) |
| 锁的公平性 | 默认非公平锁 | 可以选择公平锁或非公平锁 |
| 死锁检测 | 无法主动检测死锁 | 支持通过 tryLock() 和 lockInterruptibly() 检测 |
| 锁的信息存储 | 对象头中 | state 字段 |
| 性能 | 高并发下可能性能较差 | 性能较优,特别在高并发情况下 |
| 锁的粒度 | 锁住方法或代码块 | 提供更多的灵活性和细粒度的锁控制 |
| 支持可重入性 | 支持 | 支持 |
| 默认锁类型 | 非公平锁 | 默认非公平锁 |
| 跨服务锁支持 | 仅在同一 JVM 内有效,不能跨服务同步 | 不能跨服务锁,需使用分布式锁 |
总结
-
synchronized是 JVM 层面的锁,适合单 JVM 环境中的线程同步,但无法跨 JVM 实例(跨服务)同步,且在高并发场景下性能较差。 -
ReentrantLock提供了更多的灵活性和控制,支持手动加锁、选择公平锁与非公平锁、死锁检测等功能,适用于更复杂的并发场景,并且能够在高并发下提供较好的性能。
ReenTrantLock与Synchronized锁对比解析
2038

被折叠的 条评论
为什么被折叠?



