JAVA 锁机制【待完善】
锁是作用于在多线程环境中实现对共享资源的并发访问控制的机制,确保同一时间只有一个线程能够访问特定资源,从而保障了数据的一致性和完整性。
作用:防止多个线程同时修改共享数据导致的不一致问题,保证线程安全。
java 的锁分为两类:
- 第一类是 synchronized 同步关键字,这个关键字属于
隐式的锁,是jvm层面实现,使用的时候看不见; - 第二类是 Lock 接口以及对应的各种实现类,这属于
显式的锁,就是我们能在代码层面看到锁这个对象,而这些个对象的方法实现,大都是直接依赖CPU 指令的,无关 jvm 的实现。

锁分类
| 名称 | 说明 | 场景 |
|---|---|---|
| 独占锁 / 互斥锁(Mutex) | 确保同一时间只有一个线程能够持有,共享资源(阻塞) | ReentrantLock、Sychronized、ReentrantReadWriteLock的写锁 |
| 共享锁 | 锁可被多个线程所持有 | ReentrantReadWriteLock的读锁 |
| 读写锁(Read-Write Lock) | 允许多个线程同时读取共享资源,但在写入时只能有一个线程进行操作(阻塞), 基于互斥锁和条件变量,维护读写计数器。 | 适合读多写少的场景 ReentrantReadWriteLock、StampedLock |
| 偏向锁 | 锁会偏向于第一个获得它的线程,当这个线程再次请求锁的时候不需要进行任何同步操作,减少锁操作的开销。 研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁的代价而引入偏向锁。那么显然,一旦另一个线程尝试获得这个锁,那么偏向模式就会结束。另一方面,如果程序的大多数锁都是多个线程访问,那么偏向锁就是多余的。 | ReentrantLock |
| 轻量级锁 | 当偏向锁的条件不满足,但的确有多线程并发争抢同一锁对象时,但并发数不大时,优先使用轻量级锁。 | ReentrantLock |
| 自旋锁(Spin Lock) | 自旋锁是一个过渡锁,是从轻量级锁升级到重量级锁的过渡。也就是CAS。 适用于锁持有时间较短的场景,避免线程上下文切换的开销。但循环可能导致 CPU 资源浪费 | atomic原子类 |
| 重入锁 | 同一个线程可以多次获取同一把锁而不会导致死锁的锁。特点:需要手动释放锁,适用于需要更灵活控制的场景 | ReentrantLock |
| 公平锁 | 按照线程在队列(FIFO) 中的排队顺序,先到者先拿到锁,避免饥饿现象。 | ReentrantLock(构造函数中指定公平策略) |
| 非公平锁 | 当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 | ReentrantLock(默认非公平)、synchronized |
| 无锁编程 | 通过原子操作(如 CAS)和数据结构(如队列、栈)避免使用传统锁机制 | Atomic原子操作类 |
| 分布式锁 | 在分布式系统中基于 ZooKeeper、基于 Redis 协调多个节点对共享资源的访问. 实现复杂且需要考虑网络延迟、节点故障等问题。 |
乐观锁和悲观锁
按照锁的特性可分为分乐观锁和悲观锁两类,乐观锁(Optimistic Locking)和悲观锁(Pessimistic Locking)是两种不同的并发控制策略,用于解决多线程环境下的数据一致性问题。
| 特性 | 乐观锁 | 悲观锁 |
|---|---|---|
| 核心思想 | 假设冲突不会发生 | 假设冲突一定会发生 |
| 锁机制 | 无锁,基于版本号或 CAS | 加锁,在操作数据之前,先获取锁,确保独占访问。基于锁的同步机制 |
| 优缺点 | 高并发读性能,避免了锁竞争 实现复杂,高冲突时重试多 | 简单易实现,在高冲突时能避免数据不一致 锁的开销大,可能导致线程阻塞 |
| 适用场景 | 读多写少,低冲突 | 写频繁,高冲突 |
它们的核心区别在于对并发冲突的预期和处理方式。
悲观锁: 假设冲突一定会发生,因此在操作数据时会先加锁,确保在操作期间其他线程无法访问或修改该数据。
- 基于锁的同步机制:synchronized(内置锁) 、ReentrantLock(显式锁)
- 数据库中的悲观锁:
- 使用 SELECT … FOR UPDATE 语句锁定
行。 - 使用事务锁(如 BEGIN TRANSACTION 和 COMMIT)。
- 使用 SELECT … FOR UPDATE 语句锁定
乐观锁:假设冲突不会发生,因此在操作数据时不加锁,而是通过某种机制在提交时检查是否发生了冲突,如果冲突则重试,直到成功。
- 基于版本号(Version Number)/ 时间戳:
- 每次更新数据时,增加版本号。
- 提交时检查版本号是否发生变化,若变化则重试。
- 基于 CAS(Compare-And-Swap):
- 使用原子操作(如 AtomicInteger 或 Unsafe)来更新数据。
- 提交时通过 CAS 操作检查并更新数据。
- 其他乐观锁机制
Vector 和 CopyOnWriteArrayList:在某些情况下,可以通过这些类实现乐观锁。
ConcurrentHashMap:在某些操作中,可以通过 putIfAbsent 等方法实现乐观锁。
公平锁与非公平锁
按照锁的顺序分类: 公平锁与非公平锁
| 特性 | 公平锁 | 非公平锁 |
|---|---|---|
| 锁获取顺序 | 按照请求顺序(FIFO)获取锁 | 直接尝试抢占锁,忽略队列顺序 |
| 优缺点 | 优点是保证先到先得,避免饥饿;缺点是上下文切换频繁,吞吐量较低,因为线程频繁进入/退出等待状态。 | 优点是减少线程切换,线程直接尝试获取锁,吞吐量高;缺点是可能导致线程饥饿。 |
| 适用场景 | 按顺序访问的场景 | 对吞吐量要求高,顺序要求不高的场景 |
| 实现复杂度 | 较高,需要维护队列顺序 | 较低,直接尝试获取锁 |
构造函数中指定公平策略,默认不公平锁。ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
synchronized 也是不公平锁。
| 特性 | synchronized | ReentrantLock | ReentrantReadWriteLock | StampedLock |
|---|---|---|---|---|
| 锁类型 | 内置锁、独占锁、可重入锁 | 可重入锁、独占锁、支持公平锁 | 独占写锁,共享读锁、支持公平锁、可重入锁 | 独占写锁,共享读锁(悲观读锁和乐观读锁) |
| 支持中断 | 不支持 | 支持(lockInterruptibly) | 支持 | 不支持 |
| 尝试锁 | 支持(tryLock) | |||
| 锁的释放 | 自动释放锁 | 手动释放锁 | 手动释放锁 | 手动释放锁 |
| 支持条件变量 | 支持(wait() / notify())但功能较弱 | 支持(Condition) | 支持(Condition) | 不支持 |
| 适用场景 | 简单同步需求、低竞争场景 | 高竞争场景、复杂锁管理需求 | 读多写少, 高并发读取 | 读多写少, 引入了乐观读机制 |
Synchronized 关键字
synchronized 是一个Java 的关键字(内置的同步机制),依赖于 JVM 实现,支持独占锁、可重入锁。当对一个类或对象加锁时,线程如果要访问该类或对象必须先获得它的锁。1
可见性: 对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的。并且在释放锁之前会将对变量的修改刷新到共享内存当中,保证资源变量的可见性。原子性: 被修饰的类或对象的所有操作都是原子的,因为在执行操作之前必须先获得类或对象的锁,直到执行完才能释放。有序性: 一个变量在同一个时刻只允许一条线程对其进行 lock 操作,也就确定了线程执行同步代码块的有序性。
synchronized 和 volatile 的区别?
synchronized 和 Lock 区别?
synchronized 和 ReentrantLock 的区别?

✤ static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份。
✤ 静态成员不属于任何一个实例对象,是类成员。所以,如果线程 A、B分别 调用同一个实例对象的非静态 synchronized 方法 和 实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态方法时占用是当前类的锁,而访问非静态方法占用是当前实例对象锁。
✤ 尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
synchronized可重入的原理:
底层原理维护一个计数器,当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁。
多线程中 synchronized 锁升级:
偏向锁->自旋锁->轻量级锁->重量级锁。锁升级是为了减低了锁带来的性能消耗,按照这个顺序,锁的重量依次增加。
多线程中 synchronized 锁升级的原理:
- 在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,
- 再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,
- 如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,
- 执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
// 双重校验锁实现对象单例(线程安全)
public class Singleton {// 采用 volatile 关键字修饰(禁止JVM的指令重排), 避免在多线程下 初始化顺序混乱private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getUniqueInstance() {//先判断对象是否已经实例过,没有实例化过才进入加锁代码if (uniqueInstance == null) {//类对象加锁synchronized (Singleton.class) {if (uniqueInstance == null) {// uniqueInstance = new Singleton(); 这段代码其实是分为三步执行// 1.为 uniqueInstance 分配内存空间// 2.初始化 uniqueInstance// 3.将 uniqueInstance 指向分配的内存地址uniqueInstance = new Singleton();}}}return uniqueInstance;}
}
Lock 接口


AQS
ReentrantLock
是 Lock 接口的实现类,支持 可重入锁、独占锁、公平
