区别
- 修饰范围:
- 修饰方法:
synchronized
修饰实例方法时,锁是当前实例对象;修饰静态方法时,锁是当前类的Class
对象。它作用于整个方法体,对该方法的所有代码同步。例如:
public class SynchronizedMethodExample {
public synchronized void synchronizedMethod() {
// 方法内代码同步执行
}
}
- 修饰代码块:
synchronized
修饰代码块可以指定锁对象,可以是任意对象。它只对代码块内的代码进行同步。例如:
public class SynchronizedBlockExample {
private final Object lock = new Object();
public void synchronizedBlock() {
synchronized (lock) {
// 代码块内代码同步执行
}
}
}
- 粒度控制:
- 修饰方法:粒度较大,因为整个方法都被同步,即使方法内有部分代码不需要同步,也会被同步,可能影响性能。
- 修饰代码块:粒度较小,只对关键的需要同步的代码块进行同步,能提高程序的并发性能。
- 锁对象:
- 修饰实例方法:锁对象是当前实例,不同实例之间不会竞争同一把锁(静态方法除外)。
- 修饰静态方法:锁对象是类的
Class
对象,所有实例调用该静态方法都会竞争同一把锁。
- 修饰代码块:锁对象由开发者指定,可以是当前实例(
this
)、类的Class
对象或者其他任意对象,灵活性更高。
优先选择场景
- 优先选择修饰方法的场景:
- 方法内代码整体关联紧密:如果方法内的所有操作都需要原子性,例如一个银行账户的取款方法,涉及到余额检查、扣除等一系列操作,需要整体同步,避免在操作过程中其他线程干扰,这种情况下使用
synchronized
修饰方法比较合适。
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public synchronized void withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
}
}
}
- 优先选择修饰代码块的场景:
- 方法内部分代码需要同步:如果方法内大部分代码不需要同步,只有一小部分关键代码需要同步,使用
synchronized
修饰代码块可以减少同步范围,提高并发性能。例如在一个缓存读取方法中,只有更新缓存的部分需要同步。
public class Cache {
private Object cacheData;
private boolean isCacheValid;
public Object getCacheData() {
if (!isCacheValid) {
synchronized (this) {
if (!isCacheValid) {
// 从数据库等数据源获取数据并更新缓存
cacheData = getFromDataSource();
isCacheValid = true;
}
}
}
return cacheData;
}
private Object getFromDataSource() {
// 模拟从数据源获取数据
return new Object();
}
}
- 需要灵活控制锁对象:当需要使用不同于实例对象或类对象的锁时,如多个不同类型对象之间需要协调同步,使用
synchronized
代码块指定特定的锁对象更合适。