synchronized 与 ReentrantLock 对比

synchronizedReentrantLock 是 Java 提供的两种实现线程同步的方式,它们都能够解决多线程访问共享资源时的数据一致性问题。虽然功能相似,但它们在实现机制、性能和灵活性方面存在显著差异。

1. 基本介绍

synchronized

  • 类型:Java 的关键字,内置锁。
  • 特性:简单易用,代码结构清晰。在方法或代码块上加锁,自动管理锁的获取和释放。JVM 提供了性能优化(偏向锁、轻量级锁等)。

ReentrantLock

  • 类型:显式锁(java.util.concurrent.locks 包)。
  • 特性:提供更灵活的锁功能。手动控制锁的获取与释放,支持条件变量、可中断锁和超时机制。需要正确使用 lock() 和 unlock(),否则可能导致死锁。

2. 区别对比

特性

synchronized

ReentrantLock

实现类型

JVM 内置,依赖 Monitor 对象实现

显式锁,基于 AbstractQueuedSynchronizer (AQS)实现

锁的获取和释放

自动获取和释放,线程退出同步块后自动释放锁

手动获取(lock())和释放(unlock()),需要显式调用

可中断性

不支持

支持可中断锁,通过 lockInterruptibly()中断线程等待

公平性

不支持

支持公平锁和非公平锁(默认非公平),通过构造方法设置

性能

JDK 1.6 后性能接近,但锁粒度较粗

性能高,灵活性强,适合高并发场景

锁重入性

支持(同一线程可以多次获取锁)

支持(同一线程可以多次获取锁)

锁超时

不支持

支持,通过 tryLock(long timeout, TimeUnit unit)方法

条件变量支持

不支持

支持条件变量(Condition),可以实现更复杂的线程协调

锁升级/优化机制

偏向锁、轻量级锁、重量级锁动态升级

无锁优化机制,直接基于 AQS 实现

适用场景

简单场景,代码结构清晰,性能优化后适合大多数场景

高并发场景,复杂线程协调需求

3. 具体功能对比

(1) 可中断性

  • synchronized:当一个线程在等待 synchronized 锁时,如果被阻塞,无法被中断。
  • ReentrantLock:可以通过 lockInterruptibly() 方法实现可中断的锁机制,在等待锁时可以响应中断信号。

示例:

lock.lockInterruptibly(); // 可响应中断信号

(2) 公平性

  • synchronized:无法保证公平性,线程获取锁的顺序不一定按请求顺序。
  • ReentrantLock:支持公平锁和非公平锁(默认非公平锁)。公平锁通过构造函数设置:

(3) 锁超时

  • synchronized:线程无法设置超时时间,只能等待锁释放。
  • ReentrantLock:支持超时机制,通过 tryLock(long timeout, TimeUnit unit) 实现。

示例:

if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // 获得锁后的操作
    } finally {
        lock.unlock();
    }
} else {
    // 超时未获取锁的处理
}

(4) 条件变量

  • synchronized:不支持条件变量,线程只能通过 wait()notify() 进行协调。
  • ReentrantLock:支持多个条件变量,通过 Condition 实现更复杂的线程协作。

示例:

Condition condition = lock.newCondition();

lock.lock();
try {
    condition.await();  // 等待信号
    condition.signal(); // 发出信号
} finally {
    lock.unlock();
}

(5) 性能

  • synchronized:JDK 1.6 后性能大幅优化,支持偏向锁、轻量级锁等,适合大多数场景。
  • ReentrantLock:性能高,更适合高并发和需要额外功能(如超时、条件变量)的复杂场景。

4. 使用示例

(1) 使用 synchronized

public class SynchronizedExample {
    private int counter = 0;

    public synchronized void increment() {
        counter++;
    }

    public synchronized int getCounter() {
        return counter;
    }
}

(2) 使用 ReentrantLock

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int counter = 0;

    public void increment() {
        lock.lock(); // 获取锁
        try {
            counter++;
        } finally {
            lock.unlock(); // 确保锁被释放
        }
    }

    public int getCounter() {
        lock.lock();
        try {
            return counter;
        } finally {
            lock.unlock();
        }
    }
}

(3) 使用条件变量

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

public class ConditionExample {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void awaitSignal() throws InterruptedException {
        lock.lock();
        try {
            System.out.println("Waiting for signal...");
            condition.await(); // 等待信号
            System.out.println("Signal received!");
        } finally {
            lock.unlock();
        }
    }

    public void sendSignal() {
        lock.lock();
        try {
            System.out.println("Sending signal...");
            condition.signal(); // 发出信号
        } finally {
            lock.unlock();
        }
    }
}

5. 选择建议

  • 选择 synchronized 的场景:同步逻辑简单,代码可读性更重要。不需要锁的高级功能(如条件变量、可中断锁、超时锁)。小型项目或低并发场景。
  • 选择 ReentrantLock 的场景:高并发场景,需更高的性能和灵活性。需要公平锁、可中断锁、超时锁等高级功能。需要使用条件变量实现复杂的线程协作。
Java碎碎念 文章被收录于专栏

来一杯咖啡,聊聊Java的碎碎念呀

全部评论

相关推荐

评论
3
3
分享

创作者周榜

更多
牛客网
牛客企业服务