编程后端开发人员

描述一下 Java 中 synchronized 关键字的工作原理,在哪些情况下使用它,以及错误使用可能导致哪些问题?

用 Hintsage AI 助手通过面试

回答。

synchronized 关键字在 Java 支持多线程时出现,用于保证对来自不同线程的代码块或方法的独占访问。历史上,这是 Java 对经典的竞争条件和数据不一致性问题的回应,这些问题发生在多个线程同时访问共享数据时。

问题 在于多线程工作时如何确保操作的“原子性”和防止对象状态的矛盾变化。错误使用 synchronized 可能导致死锁(deadlock)、性能下降,甚至在选择监视对象不当的情况下导致不同步。

解决方案 — 在需保证只有一个线程同时执行该代码块的方法或代码块上使用 synchronized 关键字。应仔细选择同步对象,避免较长的临界区,对于更复杂的任务使用 java.util.concurrent 包中的专用类。

代码示例:

public class Counter { private int count = 0; public synchronized void increment() { count++; } public int getCount() { return count; } } // 在单独对象上的部分代码同步: private final Object lock = new Object(); public void complexIncrement() { synchronized (lock) { // 临界区 count++; } }

关键特点:

  • synchronized 通过选定的“监视器”阻止其他线程对代码的访问
  • 可以对整个方法或仅对单独代码块进行同步
  • 同步是一个强大的但危险的工具(死锁,性能)

具有挑战性的问题。

synchronized 方法和 synchronized 块有什么区别?

同步方法会锁定对象(如果方法是静态的,则锁定类的监视器),同步块允许明确指定监视对象,从而提供更大的灵活性。

如果在一个对象中同步不同的方法会发生什么?

如果两个方法都被标记为 synchronized(非静态),它们都将使用该对象(this)作为监视器。在一个线程执行这两个方法时,另一个线程无法进入任一方法。

是否可以同步静态方法和普通方法?它们彼此之间如何解锁?

静态方法使用类本身的监视器(Class 对象),而普通方法使用实例对象。因此,静态 synchronized 方法和普通 synchronized 方法不会相互锁定。

常见错误和反模式

  • 错误的同步对象(例如,String 或池中的对象)
  • 临界区过长或不明显,导致性能下降
  • 循环等待或死锁
  • 滥用 synchronized 而不是使用 java.util.concurrent 中的现代替代方案

生活中的例子

消极案例

开发者同步不同对象的方法,但在所有地方都使用相同的 String 作为监视器。结果是由于重用池中的字符串,造成了减速和“随机”的死锁。

优点:

  • 代码简短,编写速度更快

缺点:

  • 死锁风险高
  • 调试困难
  • 性能显著下降

积极案例

基于仅在类内创建的私有最终对象进行同步(private final Object lock),并且临界区的时间最小。

优点:

  • 确保正确的行为
  • 最小的开销
  • 代码易于阅读和维护

缺点:

  • 需要纪律和经验
  • 对初学者并不总是显而易见