MST

星途 面试题库

面试题:Java多线程同步中synchronized关键字的使用方式有哪些

请详细阐述在Java多线程编程里,synchronized关键字修饰方法和修饰代码块这两种使用方式的区别,并且分别举例说明在何种场景下会优先选择其中一种方式。
41.0万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

区别

  1. 修饰范围
    • 修饰方法synchronized修饰实例方法时,锁是当前实例对象;修饰静态方法时,锁是当前类的Class对象。它作用于整个方法体,对该方法的所有代码同步。例如:
public class SynchronizedMethodExample {
    public synchronized void synchronizedMethod() {
        // 方法内代码同步执行
    }
}
  • 修饰代码块synchronized修饰代码块可以指定锁对象,可以是任意对象。它只对代码块内的代码进行同步。例如:
public class SynchronizedBlockExample {
    private final Object lock = new Object();
    public void synchronizedBlock() {
        synchronized (lock) {
            // 代码块内代码同步执行
        }
    }
}
  1. 粒度控制
    • 修饰方法:粒度较大,因为整个方法都被同步,即使方法内有部分代码不需要同步,也会被同步,可能影响性能。
    • 修饰代码块:粒度较小,只对关键的需要同步的代码块进行同步,能提高程序的并发性能。
  2. 锁对象
    • 修饰实例方法:锁对象是当前实例,不同实例之间不会竞争同一把锁(静态方法除外)。
    • 修饰静态方法:锁对象是类的Class对象,所有实例调用该静态方法都会竞争同一把锁。
    • 修饰代码块:锁对象由开发者指定,可以是当前实例(this)、类的Class对象或者其他任意对象,灵活性更高。

优先选择场景

  1. 优先选择修饰方法的场景
    • 方法内代码整体关联紧密:如果方法内的所有操作都需要原子性,例如一个银行账户的取款方法,涉及到余额检查、扣除等一系列操作,需要整体同步,避免在操作过程中其他线程干扰,这种情况下使用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;
        }
    }
}
  1. 优先选择修饰代码块的场景
    • 方法内部分代码需要同步:如果方法内大部分代码不需要同步,只有一小部分关键代码需要同步,使用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代码块指定特定的锁对象更合适。