
锁的概述
线程安全问题:
在并发环境下,由于线程各自私有的内存不共享,当多个线程修改同一份公有的内存数据时就可能会出现数据不一致的问题。
Java中的锁:
- 按实现方式分类: Java中一般有两个实现的锁,分别是内置锁Synchronized(JVM实现)和在JUC包下的各种锁(如ReentrantLock),API 层面实现。
- 按公平性分类: 公平锁和非公平锁。公平锁严格按照先进先出的顺序获取锁;非公平锁可以插队,线程之间竞争抢锁。
- 按特性分类: 有读写锁、自旋锁、可重入锁、可中断锁等。
Synchronized锁的三种状态:
偏向锁
轻量级锁
重量级锁
Synchronized的用法
Synchronized修饰实例方法:
1 | public class Counter { |
当Synchronized修饰实例方法时,线程必须获取当前实例(this)的锁才能执行被修饰的方法。
不同的实例对象拥有独立的锁,如果
线程1调用Counter1.increment(),线程2调用Counter2.increment(),相当于两个线程并行,互不干扰,不会互相阻塞。同一时间只有一个线程执行方法,不会出现中间状态被其他线程插入,保证了原子性。
Synchronized修饰静态方法:
1 | public class Counter { |
当Synchronized修饰静态方法时,锁住的是整个类Class对象,如上面的代码里锁住的就是
Counter.Class而非实例Counter1、Counter2。JVM里一个类Class对象是唯一的。所以当前类的所有实例对象共享一把锁,如果
线程1调用Counter1,线程2调用Counter2,那么这两个线程尝试获取的锁都是Counter.Class锁,如果线程1先拿到锁,线程2就会阻塞。
Synchronized修饰代码块:
1 | public class Counter { |
Synchronized的底层实现:
对象头
每个Java对象在JVM内存中都包含一个对象头(Object Header),它是实现synchronized锁机制的基石。
Mark Word(核心动态结构):
- 动态记录:根据对象状态(无锁/偏向/轻量/重量)复用存储空间。
- 关键信息:存储了HashCode、GC分代年龄、锁状态标志、持有线程的ID等。
Klass Pointer(类型指针):
- 指向对象所属类的元数据。
- JVM通过该指针确定对象的具体类型。
图示:

监视器(Monitor)
核心定义:同步机制的载体
Monitor是一种同步工具,通常被描述为一个对象。当synchronized锁住一个对象时,本质是获得了该对象Monitor的所有权。
内部解析:
- Owner(拥有者): 当前持有Monitor锁的线程,同一时刻只有一个线程拥有所有权。
- Entry Set(等待区): 存放所有等待获取锁的线程集合,里面的线程处于阻塞状态(BLOCKED)。
- Wait Set(等待集合): 存放使用了wait()方法后释放锁并等待唤醒的线程,里面的线程处于等待状态(WAITING)。
图示:

锁的升级
偏向锁
设计目的:消除无竞争开销
针对锁总是由同一线程多次获取的场景,通过**“偏向”**该线程来避免每次获取锁都进行同步操作,从而提高性能。
核心原理:Mark Word标记
首次获取时,JVM通过CAS操作将对象头Mark Word设置为偏向锁状态并记录线程ID。后续进入只需简单检查ID是否跟记录的相同。
使用场景:单线程重复进入
适合无竞争的单线程同步场景。一旦有其他线程竞争, 偏向锁会被撤销并升级为轻量级锁。

轻量级锁
核心目的
针对多线程交替执行同步块的场景,避免重量级锁阻塞带来的高昂性能开销。
实现原理:CAS + 自旋
创建记录: JVM在线程栈帧中创建Lock Record,并复制对象头Mark Word。
CAS尝试: 通过CAS操作将对象头更新为指向Lock Record的指针。若成功则获得锁。
自旋等待: 若CAS失败(存在竞争),线程进入自旋(忙等)而非立即阻塞。
使用场景:
多线程交替执行同步块,且锁持时时间短。
图示:

重量级锁
核心目的:避免CPU空耗
当自旋等待无法获取锁或竞争激烈时,升级为重量级锁,防止CPU资源被无效占用。
实现原理:Monitor机制
自旋达到阈值或竞争加剧时,轻量级锁膨胀。未获锁的线程被阻塞并进入Monitor的Entry Set等待队列中。
使用场景:高并发竞争
适用于多线程激烈竞争同步块的场景,通过牺牲线程切换开销换取CPU效率。
图示:

为什么要有锁升级?
答: 系统并不是每时每刻都是高并发状态,很多时候就只有一两个线程在工作。如果一直使用重量级锁,操作系统一直在用户态和内核态切换,性能开销会非常大。
synchronized是怎么释放锁的?
答: synchronized会自动释放锁,在字节码层面,synchronized会先后执行monitorenter(加锁),monitorexit(释放锁)。
总结
synchronized是Java内置的、最基础的同步锁,通过对象头(Mark Word)和Monitor机制实现。它支持锁的自动升级,从偏向锁到轻量级锁,再到重量级锁,以适应不同的竞争场景,在保证正确性的同时尽可能地提高性能。