几种常用锁
1. sync
(Synchronized - 同步关键字)
- 本质:它是 Java 语言内置的一个关键字 (
synchronized
),是一种隐式锁(Implicit Lock)。 - 作用:提供一种简单的、自动的锁管理方式,用于标记一个代码块或方法,确保同一时间只有一个线程能够执行这段代码。
- 工作原理:
- 当一个线程进入一个被
synchronized
修饰的代码块或方法时,它会尝试获取与之关联的监视器锁(Monitor Lock)。 - 如果锁未被占用,线程获得锁并执行代码。
- 如果锁已被其他线程占用,该线程会进入阻塞状态,等待锁被释放。
- 当线程执行完毕(正常退出或抛出异常),它会自动释放锁。
- 当一个线程进入一个被
- 关键特性:
- 可重入性:同一线程可以多次进入同一个
synchronized
方法或代码块(因为锁的计数器会递增)。 - 自动释放:锁会在代码块执行完毕后自动释放,即使发生了异常也会释放(通过 finally 块机制)。
- 隐式监视器:锁对象通常是
this
或指定的对象实例/类对象(对于静态方法)。
- 可重入性:同一线程可以多次进入同一个
- 优点:语法简洁,使用方便,由 JVM 自动管理锁的获取和释放,不易遗漏。
- 缺点:灵活性较差(只能整块加锁)、性能相对较低(早期版本)、难以进行精细化控制(如超时获取锁、公平性设置)。
2. ReentrantLock
(可重入锁)
- 本质:它是 Java
java.util.concurrent.locks
包下的一个显式锁(Explicit Lock) 类。 - 作用:提供比
synchronized
更丰富、更灵活的锁功能。 - 工作原理:
- 你需要显式地创建一个
ReentrantLock
实例。 - 通过调用
lock()
方法获取锁。 - 在
finally
块中调用unlock()
方法释放锁(这一点非常重要,否则会导致死锁!)。
- 你需要显式地创建一个
- 关键特性:
- 可重入性:同上,支持同一线程多次获取锁。
- 显式控制:需要手动获取和释放锁,灵活性高。
- 公平性:可以在构造函数中指定锁的公平性(
true
表示公平锁,false
表示非公平锁,默认是非公平锁)。 - 中断响应:提供了
lockInterruptibly()
方法,允许在等待锁的过程中响应中断。 - 超时获取:提供了
tryLock(long time, TimeUnit unit)
方法,允许在指定时间内尝试获取锁,超时则返回 false。 - 条件变量:可以配合
Condition
接口使用,实现更复杂的线程间协作(类似Object.wait()
/notify()
但更强大)。
- 优点:功能强大,灵活可控,性能在某些场景下优于
synchronized
(尤其是在 JDK 1.6 之后,两者差距缩小,但ReentrantLock
仍有优势)。 - 缺点:使用稍微繁琐,需要手动释放锁,容易忘记在
finally
块中释放导致死锁。
3. Volatile
(易失性变量)
- 本质:它是一个变量修饰符,不是一个锁!但它与并发控制密切相关,常被称为“轻量级的同步机制”。
- 作用:确保对一个变量的修改,对其他线程立即可见(可见性);禁止指令重排序(有序性)。
- 工作原理:
- 可见性:当一个线程修改了一个
volatile
变量的值,这个新值会立即强制刷写到主内存中。其他线程在读取这个变量时,会从主内存中读取最新的值,而不是使用本地缓存中的旧值。 - 有序性:
volatile
变量的读写操作具有“happens-before”关系,禁止编译器和处理器对其进行重排序(除了必要的重排序以保证单线程语义)。
- 可见性:当一个线程修改了一个
- 关键特性:
- 不保证原子性:
volatile
不能保证复合操作的原子性(例如i++
)。它只保证单个读/写操作的原子性(对于大多数基本类型和引用类型)。 - 轻量级:相比锁,
volatile
的开销要小得多。
- 不保证原子性:
- 适用场景:主要用于标记某个状态(如
boolean flag
),或者作为状态标志位,指示某个事件的发生。不适合用作计数器或其他需要原子更新的数值。 - 优点:开销小,保证了可见性和有序性。
- 缺点:不能保证原子性,应用场景有限。
4. CAS
(Compare-And-Swap - 比较并交换)
- 本质:它是一种硬件级别的原子操作指令,是许多无锁算法的基础。Java 通过
sun.misc.Unsafe
类或java.util.concurrent.atomic
包下的原子类间接提供了 CAS 支持。 - 作用:在不使用锁的情况下,实现原子性的变量更新。
- 工作原理:
- 有三个参数:内存地址 V,期望值 A,新值 B。
- CPU 会比较内存地址 V 上的值是否等于期望值 A。
- 如果相等,就把 V 上的值替换为新值 B,并返回
true
。 - 如果不相等,不做任何操作,并返回
false
。
- 关键特性:
- 原子性:整个过程是原子的,要么全部完成,要么全部不完成。
- 无锁:不需要加锁,避免了线程阻塞和上下文切换的开销。
- 乐观锁:属于乐观锁的实现机制。它假设冲突很少发生,所以先尝试更新,失败了再重试(自旋)。
- 优点:性能极高,避免了锁的开销。
- 缺点:
- ABA 问题:如果一个值从 A 变成 B 又变回 A,CAS 会误以为它从未被修改过。可以通过添加版本号或时间戳来解决。
- 自旋消耗 CPU:如果 CAS 操作一直失败(竞争激烈),会一直自旋,消耗 CPU 资源。
- 只能保证一个共享变量的原子操作:对于多个共享变量,CAS 无法直接保证原子性,需要借助其他手段(如
AtomicReferenceFieldUpdater
)。
总结对比
特性 | sync (Synchronized) | ReentrantLock | Volatile | CAS |
---|---|---|---|---|
类型 | 关键字(隐式锁) | 类(显式锁) | 变量修饰符 | 原子操作指令 |
主要作用 | 保证原子性、可见性、有序性 | 提供更丰富的锁功能 | 保证可见性、有序性 | 实现原子性更新 |
原子性 | ✅ (代码块/方法级) | ✅ (代码块级) | ❌ (仅单次读写) | ✅ (单变量) |
可见性 | ✅ | ✅ | ✅ | ❌ (需配合 volatile ) |
有序性 | ✅ | ✅ | ✅ | ❌ (需配合 volatile ) |
可重入 | ✅ | ✅ | ❌ | ❌ |
公平性 | ❌ (非公平) | ✅ (可选) | ❌ | ❌ |
中断响应 | ❌ | ✅ | ❌ | ❌ |
超时获取 | ❌ | ✅ | ❌ | ❌ |
条件变量 | ❌ (仅 wait()/notify() ) | ✅ | ❌ | ❌ |
开销 | 较高 | 较高(但可控性强) | 很低 | 很低 |
复杂性 | 低 | 中等 | 低 | 高(实现层面) |
典型用途 | 通用同步需求 | 需要精细控制的同步需求 | 状态标志位 | 无锁算法、原子变量更新 |
一句话概括:
- 用
sync
或ReentrantLock
来保护临界区。 - 用
volatile
来保证单个变量的可见性和有序性。 - 用
CAS
来实现原子性更新,是构建无锁数据结构的基石。