synchronized关键字的作用与原理

synchronized是Java中实现线程同步的关键字,主要用于解决多线程环境下的数据竞争问题。它能确保同一时刻只有一个线程可以访问被保护的代码块或方法。synchronized通过对象内部的一个称为"监视器锁"(monitor lock)的机制实现同步,每个Java对象都有一个与之关联的监视器。

synchronized的三种使用方式

  1. 实例方法同步 在方法声明中添加synchronized关键字,锁的是当前实例对象(this)。不同实例的对象锁互不干扰。
public synchronized void method() {
    // 线程安全代码
}
  1. 静态方法同步 锁的是当前类的Class对象,属于类级别锁。所有实例共享同一把锁。
public static synchronized void staticMethod() {
    // 线程安全代码
}
  1. 同步代码块 可以更细粒度地控制锁的范围和对象,提高并发性能。
public void method() {
    synchronized(this) {  // 可以指定任意对象作为锁
        // 线程安全代码
    }
}

synchronized的底层实现机制

JVM通过monitorenter和monitorexit两个字节码指令实现同步。当线程进入同步块时执行monitorenter获取锁,退出时执行monitorexit释放锁。每个对象都有一个关联的monitor,包含以下关键字段:

  • _owner:当前持有锁的线程
  • _recursions:重入次数计数器
  • _EntryList:等待获取锁的线程队列

锁的升级过程

JDK6后对synchronized进行了优化,引入锁升级机制:

  1. 无锁状态:对象刚创建时的初始状态
  2. 偏向锁:适用于只有一个线程访问同步块的场景,通过CAS设置线程ID
  3. 轻量级锁:当有多个线程轻度竞争时,通过CAS自旋尝试获取锁
  4. 重量级锁:竞争激烈时升级为操作系统层面的互斥锁

使用注意事项

  1. 避免锁粒度过大:尽量缩小同步代码块范围,只保护必要的共享数据
  2. 防止死锁:避免嵌套获取多个锁,或按固定顺序获取锁
  3. 考虑性能影响:重量级锁会引发线程上下文切换,高并发场景考虑其他并发工具
  4. 锁对象选择:使用专门的对象作为锁而非业务对象,避免意外暴露

示例:线程安全的计数器实现

public class Counter {
    private int count;
    private final Object lock = new Object(); // 专用锁对象
    
    public void increment() {
        synchronized(lock) {
            count++;
        }
    }
    
    public int getCount() {
        synchronized(lock) {
            return count;
        }
    }
}

与其他同步机制的对比

  1. 与ReentrantLock比较
  • synchronized是JVM内置特性,ReentrantLock是API层面的实现
  • ReentrantLock提供更灵活的锁获取方式(可中断、超时、公平锁等)
  • synchronized在JDK6后性能差距已不明显
  1. 与volatile比较
  • volatile保证可见性和有序性,但不保证原子性
  • synchronized保证原子性、可见性和有序性

最佳实践建议

  1. 优先使用同步代码块而非同步方法,缩小锁范围
  2. 对读写比例高的场景考虑读写锁(ReadWriteLock)
  3. 明确文档记录哪些对象被用作锁
  4. 在复杂同步需求下考虑java.util.concurrent包中的高级工具类

synchronized作为Java最基础的同步机制,理解其原理和使用方式是多线程编程的重要基础。随着JDK版本的演进,其性能已大幅优化,在大多数场景下仍然是简单可靠的选择。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐