一、同步方法(锁住整个方法)
1. 代码示例
public class Counter {private int count = 0;// 同步方法:锁住整个方法public synchronized void add() {count++;}// 同步方法:锁住整个方法public synchronized void subtract() {count--;}
}
2. 原理
- 锁对象:
synchronized
修饰普通方法时,锁的是当前对象(this
)。 - 锁范围:整个方法体被锁定,其他线程无法访问该对象的任何同步方法。
3. 生活比喻
假设有一个 超市储物柜管理员:
- 管理员一次只能服务一个人(同步方法)。
- 当你在存包时(调用
add
方法),其他人不能存包也不能取包(调用任何同步方法)。
二、同步代码块(锁住部分代码)
1. 代码示例
public class Counter {private int count = 0;private final Object lock = new Object(); // 自定义锁对象public void add() {// 非同步代码(可并发执行)System.out.println("处理存包请求...");// 同步代码块:锁住关键部分synchronized(lock) {count++;}}public void subtract() {// 非同步代码(可并发执行)System.out.println("处理取包请求...");// 同步代码块:锁住关键部分synchronized(lock) {count--;}}
}
2. 原理
- 锁对象:可以指定任意对象(如
lock
或this
)。 - 锁范围:只锁定代码块内部,其他代码可并发执行。
3. 生活比喻
同一个 超市储物柜管理员:
- 管理员的大部分工作是协调(非同步代码),比如回答顾客问题。
- 只有开柜子存取物品时(同步代码块) 需要独占操作,其他时间可以处理其他请求。
三、关键区别总结
特性 | 同步方法 | 同步代码块 |
---|---|---|
锁对象 | 默认是 this (对象锁) | 可自由指定(更灵活) |
锁粒度 | 粗粒度(整个方法) | 细粒度(仅关键代码) |
性能 | 较低(锁范围大,竞争多) | 较高(锁范围小,竞争少) |
适用场景 | 简单逻辑,方法整体需要保护 | 需要精细化控制锁的代码段 |
四、实际开发中的选择建议
1. 优先用同步代码块
- 原因:缩小锁范围,提高并发效率。
- 场景:方法中只有部分代码涉及共享资源操作。
public void transfer(Account target, int amount) {// 非同步代码(比如日志记录)log("转账操作开始...");// 同步代码块(锁定两个账户)synchronized(this) {synchronized(target) {this.balance -= amount;target.balance += amount;}} }
2. 同步方法的适用场景
- 简单场景:整个方法都需要保护,且逻辑简单。
public synchronized void reset() {// 重置所有数据this.balance = 0;this.transactionCount = 0; }
3. 避免的陷阱
- 错误示例:在同步方法中调用另一个同步方法。
public synchronized void methodA() {methodB(); // 如果 methodB 也是同步方法,会导致锁重入(允许,但需注意设计) }public synchronized void methodB() {// ... }
- 锁重入:Java 允许同一个线程重复获取已持有的锁,但要避免过度嵌套。
五、扩展知识:锁对象的选择
- 建议自定义锁:避免直接使用
this
或类对象(ClassName.class
),防止与其他代码冲突。private final Object dataLock = new Object(); // 专用锁对象public void updateData() {synchronized(dataLock) { // 操作共享数据} }
总结
- 同步方法:简单粗暴,适合保护整个方法。
- 同步代码块:灵活高效,适合精细化控制。
核心思想:用最小的锁范围,实现线程安全,同时最大化并发性能!