Java的synchronized:从入门到"秃头"的终极指南
一、介绍:synchronized是什么?
synchronized
是Java中的"保安大哥",专门负责在多线程环境下维持秩序,确保共享资源不被"哄抢"。它的核心使命是解决线程安全问题,保证原子性(操作不可分割)、可见性(修改立即可见)和有序性(代码顺序执行)。
为什么需要它?
想象两个线程同时修改银行账户余额,如果没有同步机制,结果可能像两个人在同一张纸上乱涂乱画,最终余额变成谜团。而synchronized
会大喊:"排好队,一个一个来!"。
二、用法:三种姿势,总有一款适合你
-
修饰实例方法
锁住当前对象实例,适用于保护对象级别的共享资源。
javapublic synchronized void transferMoney() { /* 转账逻辑 */ }
-
修饰静态方法
锁住整个类,适用于保护类级别的静态资源。
javapublic static synchronized void updateConfig() { /* 更新全局配置 */ }
-
修饰代码块
灵活指定锁对象,适合只保护部分代码的场景。
javapublic void addToCart() { synchronized (cartLock) { /* 加购逻辑 */ } }
注意 :别用
String
或基本类型包装类当锁,容易踩坑(比如字符串常量池的缓存问题)。
三、案例:代码中的"锁"事剧场
场景1 :普通方法同步
两个线程调用同一个对象的同步方法,结果像排队上厕所------必须等前一个人出来。
场景2 :静态方法同步
即使创建了多个对象实例,静态方法同步也会锁住整个类,相当于"全店打烊,只服务一个人"。
场景3 :代码块同步
只锁住关键代码,其他代码自由飞翔,性能和安全性兼得。
四、原理:锁的"变形记"
从Java 6开始,synchronized
开启了"智能模式",根据竞争激烈程度自动升级锁状态:
- 无锁:对象刚出生,人畜无害。
- 偏向锁:假设只有一个线程访问,直接在对象头贴个"VIP标签"(线程ID),省去加锁步骤。
- 轻量级锁:多个线程交替访问,通过CAS自旋(像在锁门口徘徊)尝试获取锁,避免阻塞。
- 重量级锁:竞争激烈时,召唤操作系统级互斥量,线程排队等叫号,性能开销大但公平。
锁升级的目的:像饭店老板根据客流量调整服务员数量,既省成本又保效率。
五、对比:synchronized vs 其他同步工具
-
synchronized vs volatile
volatile
只管可见性,不保原子性(比如i++
仍需同步)。synchronized
是"全能选手",但可能引发线程阻塞。
-
synchronized vs ReentrantLock
synchronized
自动释放锁,ReentrantLock
需手动unlock()
(忘写就死锁警告!)。ReentrantLock
支持可中断、公平锁、多条件变量,适合复杂场景。
六、避坑指南:别让锁"锁"住你的智商
-
不完全同步
java// set方法同步,get方法不同步 → 可能读到旧值! public synchronized void setX(int x) { this.x = x; } public int getX() { return x; } // 应加synchronized或用volatile
-
锁对象中途变心
javasynchronized (array[0]) { array[0] = new Object(); // 锁对象被替换,同步失效! }
-
在循环中滥用锁
频繁加锁会导致性能"雪崩",改用原子类(如
AtomicInteger
)或并发集合更香。
七、最佳实践:做个锁的"时间管理大师"
- 锁粒度要细:只锁必要代码,像只保护保险箱而不是整个房间。
- 用final对象当锁:防止锁对象被意外替换。
- 避免嵌套锁:小心死锁,像两个人互相等对方先放手。
- 优先选代码块:比锁方法更灵活,减少阻塞范围。
八、面试考点:高频灵魂拷问
- 锁升级过程(偏向锁→轻量级锁→重量级锁)。
- synchronized和ReentrantLock的区别(自动vs手动、功能扩展性)。
- 为什么synchronized是非公平锁 ?
(答:唤醒线程随机,插队更高效,但可能"饿死"老实线程)。
九、总结:synchronized的"人生哲学"
- 优点:简单易用、自动释放、可重入。
- 缺点:无法中断、不支持超时、非公平。
- 适用场景 :低竞争或简单同步需求;高竞争时考虑
ReentrantLock
或StampedLock
。
最后提醒:锁虽好,但别滥用!多线程编程就像开车,安全第一,性能第二。
参考资料:
(想要更深入细节?点击链接直达原文,开启"秃头"之旅吧!)