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的碎碎念呀

全部评论

相关推荐

bg双非本科,方向是嵌入式。这次秋招一共拿到了 8 个 offer,最高年包 40w,中间也有一段在海康的实习经历,还有几次国家级竞赛。写这篇不是想证明什么,只是想把自己走过的这条路,尽量讲清楚一点,给同样背景的人一个参考。一、我一开始也很迷茫刚决定走嵌入式的时候,其实并没有一个特别清晰的规划。网上的信息很零散,有人说一定要懂底层,有人说项目更重要,也有人建议直接转方向。很多时候都是在怀疑:1.自己这种背景到底有没有机会2.现在学的东西到底有没有用3.是不是已经开始晚了这些问题,我当时一个都没答案。二、现在回头看,我主要做对了这几件事第一,方向尽早确定,但不把自己锁死。我比较早就确定了嵌入式这个大方向,但具体做哪一块,是在项目、竞赛和实习中慢慢调整的,而不是一开始就给自己下结论。第二,用项目和竞赛去“证明能力”,而不是堆技术名词。我不会刻意追求学得多全面,而是确保自己参与的每个项目,都能讲清楚:我负责了什么、遇到了什么问题、最后是怎么解决的。第三,尽早接触真实的工程环境。在海康实习的那段时间,对我触动挺大的。我开始意识到,企业更看重的是代码结构、逻辑清晰度,以及你能不能把事情说清楚,而不只是会不会某个知识点。第四,把秋招当成一个需要长期迭代的过程。简历不是一次写完的,面试表现也不是一次就到位的。我会在每次面试后复盘哪些问题没答好,再针对性补。三、我踩过的一些坑现在看也挺典型的:1.一开始在底层细节上纠结太久,投入产出比不高2.做过项目,但前期不会总结,导致面试表达吃亏3.早期有点害怕面试,准备不充分就去投这些弯路走过之后,才慢慢找到节奏。四、给和我背景相似的人一点建议如果你也是双非,准备走嵌入式,我觉得有几件事挺重要的:1.不用等“准备得差不多了”再投2.项目一定要能讲清楚,而不是做完就算3.不要只盯着技术,多关注表达和逻辑很多时候,差的不是能力,而是呈现方式。五、写在最后这篇总结不是标准答案,只是我个人的一次复盘。后面我会陆续把自己在嵌入式学习、竞赛、实习和秋招中的一些真实经验拆开来讲,希望能对后来的人有点帮助。如果你正好也在这条路上,希望你能少走一点弯路。
点赞 评论 收藏
分享
评论
3
6
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务