在Java中,ReentrantLocksynchronized都是用于实现线程同步的机制,但它们在功能、性能和使用场景上存在一些差异。以下是对两者的详细对比和示例代码:

一、核心区别

特性synchronizedReentrantLock
锁实现机制JVM内置锁,通过对象头Mark Word实现JDK层面的API(java.util.concurrent
锁获取方式隐式获取/释放(代码块/方法结束自动释放)显式获取/释放(lock()unlock()
可重入性支持(同一线程可重复获取同一把锁)支持(需手动释放相同次数)
公平性非公平锁(无法保证获取顺序)可配置公平/非公平(通过构造函数指定)
锁中断不可中断(除非抛出异常)可中断(lockInterruptibly()方法)
超时机制不支持支持(tryLock(long timeout, TimeUnit unit)
条件变量单一条件(wait()/notify()可绑定多个条件(newCondition()
性能(高并发)JDK 6+后优化显著,轻量级场景更优复杂场景(如锁中断、超时)性能更优
用法修饰普通方法、静态方法和代码块只能用在代码块

二、代码示例

1. synchronized关键字

public class SynchronizedExample {
    private int count = 0;

    // 同步方法(锁对象为this)
    public synchronized void increment() {
        count++;
    }

    // 同步代码块(锁对象为obj)
    public void incrementWithBlock() {
        Object obj = new Object();
        synchronized (obj) {
            count++;
        }
    }
}

2. ReentrantLock类

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private int count = 0;
    private final Lock lock = new ReentrantLock(); // 默认非公平锁

    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++;
        } finally {
            lock.unlock(); // 必须在finally中释放锁
        }
    }

    // 公平锁示例
    private final Lock fairLock = new ReentrantLock(true);

    // 可中断锁示例
    public void doWork() throws InterruptedException {
        fairLock.lockInterruptibly();
        try {
            // 执行任务
        } finally {
            fairLock.unlock();
        }
    }
}

3. 条件变量对比

// synchronized + wait/notify
public class SyncConditionExample {
    private final Object lock = new Object();
    private boolean condition = false;

    public void await() throws InterruptedException {
        synchronized (lock) {
            while (!condition) {
                lock.wait(); // 等待条件满足
            }
        }
    }

    public void signal() {
        synchronized (lock) {
            condition = true;
            lock.notifyAll(); // 唤醒等待线程
        }
    }
}

// ReentrantLock + Condition
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockConditionExample {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean ready = false;

    public void await() throws InterruptedException {
        lock.lock();
        try {
            while (!ready) {
                condition.await(); // 等待条件
            }
        } finally {
            lock.unlock();
        }
    }

    public void signal() {
        lock.lock();
        try {
            ready = true;
            condition.signalAll(); // 唤醒等待线程
        } finally {
            lock.unlock();
        }
    }
}

三、适用场景选择

  1. 优先使用synchronized的场景

    • 代码简单,不需要高级锁特性(如中断、超时)。
    • 锁竞争不激烈,轻量级同步场景。
    • 示例:简单的计数器、单例模式的双重检查锁。
  2. 优先使用ReentrantLock的场景

    • 需要可中断锁、公平锁或超时机制。
    • 需要绑定多个条件变量(如生产者-消费者模型)。
    • 锁竞争激烈,需优化性能(如读写锁分离)。
    • 示例:高性能缓存的并发控制、分布式系统的本地锁。

四、性能对比

  • JDK 6之前ReentrantLock性能明显优于synchronized
  • JDK 6+synchronized通过锁升级(偏向锁→轻量级锁→重量级锁)优化,性能与ReentrantLock接近。
  • 极端高并发ReentrantLock的可伸缩性更好(如使用公平锁减少线程饥饿)。

五、注意事项

  1. ReentrantLock必须手动释放锁

    lock.lock();
    try {
        // 业务逻辑
    } finally {
        lock.unlock(); // 避免死锁
    }
  2. 公平锁的性能代价

    • 公平锁会降低吞吐量(约20%-30%),仅在需要严格顺序时使用。
  3. synchronized的优化

    • 锁粗化(Lock Coarsening):将多个连续的锁操作合并为一个。
    • 锁消除(Lock Elimination):去除不可能存在竞争的锁。

六、总结

  • 简单场景:优先使用synchronized(简洁、自动释放)。
  • 高级特性:使用ReentrantLock(可中断、公平锁、多条件变量)。
  • 性能敏感:在高竞争场景下测试两者性能差异后选择。

大多数情况下,synchronized已能满足需求,仅在需要高级特性时才考虑ReentrantLock

最后修改:2025 年 06 月 18 日
如果觉得我的文章对你有用,请随意赞赏