synchronized 全方位总结(特性 + 使用方式 + 锁机制)
目录
一、前言
synchronized 是 Java 内置的悲观互斥锁关键字,核心作用是解决多线程并发下共享变量的线程安全问题,保证多线程操作的原子性、可见性和有序性。
与 ReentrantLock 相比,它无需手动加锁、解锁,由 JVM 底层自动管理,语法简单、安全性高,是日常并发编程中最基础、最常用的同步方案。
二、synchronized 六大核心特性
synchronized 的核心特性决定了它的同步能力,逐一拆解如下:
1. 原子性(核心特性)
被 synchronized 修饰的代码块/方法,同一时刻只能被一个线程执行,操作不可拆分、不可中断。
举例:多线程同时修改一个共享变量,同步后不会出现数据争抢、脏数据(如多线程累加同一个变量,最终结果正确)。
2. 可见性
当一个线程修改了 synchronized 保护的共享变量后,会立刻将修改后的值刷新到主内存,其他线程再次读取该变量时,能直接获取最新值,解决了多线程内存不可见问题。
3. 有序性
禁止 JVM 对同步范围内的代码进行指令重排序,保证代码按编写顺序执行,避免因重排序导致的并发逻辑错乱。
4. 可重入性
同一个线程在获取锁之后,再次进入由同一把锁保护的同步代码/方法时,无需重新争抢锁,可直接获取,不会出现自己阻塞自己的死锁情况。
举例:同步方法 A 内部调用同步方法 B(两把锁为同一对象),线程进入 A 后,可直接进入 B,无需再次加锁。
5. 自动释放锁
无需手动解锁,JVM 自动管理锁的释放,两种场景下会自动释放:
-
同步代码/方法正常执行结束,自动释放锁;
-
同步代码/方法抛出异常,无论异常类型,都会自动释放锁,避免死锁。
6. 非公平锁(默认)
synchronized 默认是非公平锁,新来的线程可以直接抢占锁,不保证线程排队顺序,这样的设计能提升并发吞吐效率。
三、synchronized 三种使用方式
synchronized 有三种使用场景,核心区别在于锁对象不同,粒度不同,适用场景也不同,优先推荐同步代码块(粒度更细、性能更好)。
1. 修饰普通实例方法
锁对象:当前实例对象(this)
特点:同一实例对象的多个线程互斥,不同实例对象之间互不影响,不互斥。
public class SyncDemo {
// 修饰普通实例方法
// 锁对象:SyncDemo 的实例对象(this)
public synchronized void testSyncMethod() {
// 同步业务逻辑(如修改共享变量)
System.out.println("普通实例方法同步");
}
}
2. 修饰静态方法
锁对象:当前类的 Class 对象(全局唯一)
特点:锁住整个类,所有该类的实例对象共用同一把锁,全局互斥。
public class SyncDemo {
// 修饰静态同步方法
// 锁对象:SyncDemo.class 类对象
public static synchronized void testSyncStaticMethod() {
System.out.println("静态方法全局同步");
}
}
- 普通同步方法:锁对象 this,同对象互斥,不同对象不互斥。
- 静态同步方法:锁 Class 类对象,全局唯一,所有对象都互斥。
3. 修饰代码块(推荐使用)
锁对象:可手动指定(this、自定义对象、Class 对象),灵活度最高。
特点:只锁住关键同步代码,不锁整个方法,锁粒度更细,并发效率更高。
① 锁 this 当前实例
public void testSyncBlockThis() {
synchronized (this) {
System.out.println("同步代码块 - 锁 this");
}
}
② 锁自定义对象(项目最常用)
public class SyncDemo {
// 单独定义锁对象
private final Object lock = new Object();
public void testSyncBlockObj() {
// 锁自定义对象
synchronized (lock) {
System.out.println("同步代码块 - 锁自定义对象");
}
}
}
③ 锁 Class 类对象
public void testSyncBlockClass() {
synchronized (SyncDemo.class) {
System.out.println("同步代码块 - 锁 Class 对象");
}
}
四、synchronized 锁机制原理(重点)
synchronized 的底层由 JVM 实现,核心依赖 Monitor 监视器和对象头,JDK 1.6 对其进行了重大优化,引入锁升级机制,大幅提升性能。
1. 锁的本质:锁住的是对象
synchronized 锁的不是代码,而是对象。每一个 Java 对象底层都包含一个 对象头(Mark Word),锁的标记、锁状态、持有线程等信息,都存储在对象头中。
简单理解:给代码加 synchronized,本质是给指定的对象“上锁”,线程要执行同步代码,必须先获取该对象的锁。
2. JDK 1.6 锁升级机制
为了优化 synchronized 的性能,JVM 引入了“锁升级”机制,锁会根据竞争激烈程度,从低开销向高开销逐级升级,只能升级,不能降级。
public class SyncDemo {
// 隐含:锁 = 当前实例对象 this
public synchronized void testSyncMethod() {
System.out.println("普通实例方法同步");
}
}
先记住 3 个前提
- 你
new SyncDemo()出来的每一个对象,头上都有一块「小标签」叫 Mark Word(对象头)。synchronized不加锁时、加轻锁、加重锁,状态都记在这个对象头里。- 锁升级只能往上升级,不能降级。
(1)什么是锁升级?
JDK1.6 之前:只要用 synchronized 一上来就是重量级锁,开销特别大,很慢。
JDK1.6 之后做了优化:根据竞争线程多少,自动一步步升级:偏向锁 → 轻量级锁 → 重量级锁
就像坐车:没人抢 → 独享专车(偏向锁)少数人抢 → 轮流排队不停车(轻量级锁自旋)很多人疯抢 → 只能进车站候车室等(重量级锁阻塞)
(2)三级锁逐个讲解
1. 偏向锁(没人抢、只有一个线程用)
场景:整个程序就一个线程反复调用你的 testSyncMethod(),没有其他线程来抢。
特点:
- JVM 默认偏向第一个进来的线程
- 直接放行,几乎没有加锁开销
- 就像这个房间专门给你一个人专用,不用每次开锁关门
👉 适用:单线程操作同步方法,无竞争。
2. 轻量级锁(少量线程抢、竞争不激烈)
场景:来了两三个线程轮流抢同一个对象的 testSyncMethod()。
特点:
- 不把线程挂起、不进操作系统阻塞
- 线程在原地循环重试抢锁(叫:自旋)
- 谁抢到谁执行,抢不到就一直循环试
比喻:两个人抢一间办公室,没抢到不坐下等,就在门口来回踱步、不停看什么时候空出来,不睡觉阻塞。
👉 适用:线程少、抢锁时间很短。
3. 重量级锁(很多线程疯狂竞争)
场景:一堆线程同时抢同一个对象的同步方法,自旋抢不到,一直空转浪费 CPU。
这时直接升级为重量级锁:
- 抢不到锁的线程直接阻塞,进入等待队列
- 让出 CPU,不瞎循环了
- 依靠操作系统内核调度唤醒
比喻:人太多了,门口踱步没用,直接去候车室坐着等,有空了再叫你。
缺点:开销最大、最慢;优点:不浪费 CPU。
(3)、关键总结(背这几句就够)
- 锁升级顺序:偏向锁 → 轻量级锁 → 重量级锁,只能升不能降。
- 偏向锁:单线程无竞争,开销最小。
- 轻量级锁:少量竞争,线程自旋不阻塞。
- 重量级锁:高并发竞争,线程阻塞等待,开销最大。
- 所有锁状态,都存在Java 对象的对象头 Mark Word 里,你那个
SyncDemo实例对象自带这个标记。
3. 等待唤醒机制
synchronized 可配合 Object 类的wait()、notify()、notifyAll() 方法,实现线程的等待与唤醒,常用于生产者-消费者模型。
wait():线程释放持有的锁,进入等待池阻塞,等待被唤醒;
notify():随机唤醒一个处于等待池中的线程,唤醒后线程需重新争抢锁;
notifyAll():唤醒所有处于等待池中的线程,所有线程重新争抢锁。
注意:wait()、notify()、notifyAll() 必须在 synchronized 同步代码块/方法中调用,否则会抛出 IllegalMonitorStateException 异常。
4. 底层实现核心
-
synchronized 底层基于 Monitor 监视器实现,Monitor 是 JVM 内部的一个对象,用于管理锁的竞争和线程等待;
-
重量级锁依赖操作系统的 互斥量(Mutex) 实现,线程阻塞和唤醒会切换内核态与用户态,开销较大;
-
锁的状态(偏向锁、轻量级锁、重量级锁),均存储在对象头的 Mark Word 中,JVM 通过修改 Mark Word 的值,实现锁的升级和切换。
五、使用注意事项
-
优先使用 同步代码块,缩小锁粒度,只锁住关键代码,避免锁住整个方法,提升并发性能;
-
不要用 String、Integer 等常量、包装类作为锁对象,这类对象可能被缓存,导致多个线程共用同一把锁,引发意外互斥;
-
线程等待时,建议用
while循环判断等待条件,而非if,防止线程被虚假唤醒后,跳过条件判断直接执行代码; -
简单并发场景(无特殊需求)优先用 synchronized,语法简单、不易出错;复杂场景(需要公平锁、超时等待、多条件唤醒)再考虑 ReentrantLock。
六、博客总结
synchronized 作为 Java 内置的同步机制,核心优势是简单、安全、无需手动管理锁的释放,其核心特性(原子性、可见性、有序性、可重入性)保证了多线程并发安全。
三种使用方式对应不同的锁对象,同步代码块因粒度细、性能优,是日常开发的首选;底层基于对象头和 Monitor 实现,JDK 1.6 的锁升级机制(偏向锁→轻量级锁→重量级锁)大幅优化了其性能。
更多推荐



所有评论(0)