Java synchronized案例怎么用:从入门到实战的完整指南
目录导读
- synchronized核心概念速览
- 基础用法案例:同步方法与同步块
- 经典实战:多线程卖票系统
- 进阶陷阱:错误使用案例与修正
- 性能对比:synchronized vs Lock
- 常见问答FAQ
- 总结与最佳实践
synchronized核心概念速览
Q: synchronized到底锁的是什么?
A: 锁的是对象实例或Class对象。

- 修饰实例方法:锁当前实例
this - 修饰静态方法:锁当前类的
Class对象 - 修饰代码块:锁括号内指定的对象
关键原则:多个线程必须竞争同一把锁才能实现互斥,如果锁不同,synchronized将失效。
基础用法案例:同步方法与同步块
案例1:同步实例方法
public class Counter {
private int count = 0;
public synchronized void increment() { count++; }
// 等价于 synchronized(this) { count++; }
}
适用场景:整个方法都需要同步,且锁对象明确。
案例2:同步静态方法
public class StaticCounter {
private static int count = 0;
public static synchronized void increment() { count++; }
// 锁Class对象,所有实例共享同一把锁
}
案例3:同步代码块(精准锁定)
public class FineGrainedLock {
private final Object lock = new Object();
public void method() {
// 非同步代码...
synchronized (lock) {
// 仅同步临界区代码
}
}
}
优势:缩小同步范围,提升并发性能。
经典实战:多线程卖票系统
问题描述
模拟3个窗口同时卖100张票,要求:
- 不能超卖(同一张票不能卖两次)
- 最终剩余票数为0
错误实现(线程不安全)
class Ticket {
private int tickets = 100;
public void sell() {
if (tickets > 0) {
try { Thread.sleep(10); } catch (InterruptedException e) {}
tickets--;
}
}
}
// 3个线程各卖100次,结果剩余票数可能为负数
正确实现(synchronized锁实例)
class SafeTicket {
private int tickets = 100;
public synchronized void sell() { // 锁this
if (tickets > 0) {
try { Thread.sleep(10); } catch (InterruptedException e) {}
tickets--;
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
SafeTicket ticket = new SafeTicket();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
while (ticket.available() > 0) {
ticket.sell();
}
}).start();
}
}
}
运行结果:剩余票数始终为0,无负数。
注意事项
- 锁必须一致:若使用
SafeTicket的不同实例,锁失效 - 避免在锁内执行耗时I/O:会阻塞其他线程
进阶陷阱:错误使用案例与修正
陷阱1:锁字符串常量
// 错误:字符串常量池导致不同对象共享同一锁
synchronized ("LOCK") { ... }
// 修正:使用new Object()或指定唯一对象
private final Object lock = new Object();
synchronized (lock) { ... }
陷阱2:锁Integer等包装类
// 错误:Integer缓存池(-128~127)导致锁范围扩大
Integer lock = 1;
synchronized (lock) { ... }
// 修正:使用最终常量或专用锁对象
private final Object lock = new Object();
陷阱3:synchronized与非同步混用
class Data {
private int value;
public synchronized void set(int v) { value = v; }
public int get() { return value; } // 未同步!导致读取脏数据
}
// 修正:读取方法也要同步,或使用volatile+原子类
性能对比:synchronized vs Lock
| 特性 | synchronized | Lock (ReentrantLock) |
|---|---|---|
| 锁获取方式 | 隐式 | 显式lock()/unlock() |
| 可中断性 | 不支持 | 支持lockInterruptibly() |
| 公平锁 | 非公平 | 可选公平/非公平 |
| 性能 | 低竞争下优于Lock | 高竞争下更稳定 |
| 语法简洁 | 自动释放锁 | 必须finally解锁 |
何时用synchronized:
- 同步块简单,无需高级特性
- 代码可读性优先
- 低并发场景
何时用Lock:
- 需要超时、可中断、尝试获取锁
- 需要公平锁
- 高并发写场景
常见问答FAQ
Q1:synchronized能锁住int吗?
A:不能,synchronized只能锁对象,int是原始类型,需用Integer包装类,但注意缓存陷阱。
Q2:静态方法用synchronized锁类,会影响实例方法吗?
A:不会,实例方法锁this,静态方法锁Class,两者互不干扰。
Q3:synchronized遇到异常会释放锁吗?
A:会,JVM自动释放锁,即使发生异常或return。
Q4:多个synchronized方法嵌套是否会导致死锁?
A:可能,例如A线程锁obj1请求obj2,B线程锁obj2请求obj1,解决方案:全局排序锁顺序。
Q5:String作为锁对象是否安全?
A:不安全,字符串常量池导致不同线程可能持有相同锁对象,用new Object()确保唯一性。
总结与最佳实践
核心原则
- 锁对象唯一:避免使用字符串常量、Integer缓存
- 锁定范围最小:能用同步块不用方法,能用代码块不用全方法
- 锁内的操作尽量轻量:避免I/O、网络请求等耗时操作
- 使用专用锁对象:声明为
private final Object,避免外部干扰
代码模板
public class ThreadSafeService {
private final Object lock = new Object();
private final Map<String, String> cache = new HashMap<>();
public String getData(String key) {
// 非同步预处理
synchronized (lock) {
// 1. 检查缓存
if (cache.containsKey(key)) return cache.get(key);
// 2. 计算数据(耗时操作)
String result = computeExpensive(key);
// 3. 放入缓存
cache.put(key, result);
return result;
}
}
}
避坑清单
- [x] 锁是否可能为
null? - [x] 是否存在字符串常量池风险?
- [x] 锁内的代码是否可能长时间阻塞?
- [x] 是否所有写操作都加了同步?
- [x] 读取操作是否需要同步保证可见性?
通过本文的案例和陷阱分析,你应该能从容应对90%的Java同步开发场景。简单问题用synchronized,复杂场景用Lock,选对工具事半功倍。