【Java 开发日记】我们来说一下 synchronized 与 ReentrantLock 的区别

【Java 开发日记】我们来说一下 synchronized 与 ReentrantLock 的区别

目录

一、基本特性对比

二、详细区别分析

1. 实现层面

2. 使用方式

3. 公平性选择

4. 条件变量(Condition)

5. 中断与超时

6. 性能差异

三、适用场景

优先使用 synchronized 的情况

优先使用 ReentrantLock 的情况

四、示例对比

场景:生产者-消费者模型

五、总结

面试回答


一、基本特性对比

特性

synchronized

ReentrantLock

锁的实现机制

JVM 内置关键字,通过监视器实现

JDK 提供的 API 类(java.util.concurrent.locks

锁的获取方式

隐式获取和释放(进入/退出同步代码块或方法自动获取/释放)

显式调用 lock()/unlock()方法

可重入性

支持

支持

锁的类型

非公平锁(默认)

可选择公平锁或非公平锁(构造函数指定)

条件变量

通过 wait()/notify()/notifyAll()实现

通过 Condition对象支持多个条件队列

中断响应

不支持中断等待

支持 lockInterruptibly()中断等待

超时机制

不支持

支持 tryLock(timeout, unit)尝试获取锁

锁的绑定

与代码块或方法绑定

可跨方法绑定,更灵活

性能

JDK 1.6 后优化,性能接近

在高并发竞争下表现更稳定

二、详细区别分析

1. 实现层面

  • synchronized
    • Java 关键字,由 JVM 底层实现(通过 monitorenter/monitorexit 字节码指令)。
    • 锁信息记录在对象头的 Mark Word 中。
  • ReentrantLock
    • 基于 AbstractQueuedSynchronizer(AQS) 实现的显式锁。
    • 通过 CAS(Compare-And-Swap)和队列管理线程竞争。

2. 使用方式

// synchronized 隐式使用 public synchronized void method() { // 同步代码 } // 或 public void method() { synchronized(this) { // 同步代码 } } // ReentrantLock 显式使用 private ReentrantLock lock = new ReentrantLock(); public void method() { lock.lock(); try { // 同步代码 } finally { lock.unlock(); // 必须手动释放 } }

3. 公平性选择

  • synchronized:仅支持非公平锁(线程竞争时随机获取锁)。
  • ReentrantLock
    • 公平锁:按等待时间顺序获取锁,避免线程饥饿,但性能较低。
    • 非公平锁:允许插队,性能更高。

4. 条件变量(Condition)

  • synchronized:通过 Object.wait()/notify() 实现等待/唤醒,只能有一个等待队列。
  • ReentrantLock:可创建多个 Condition 对象,实现精细化的线程等待/唤醒。
Condition condition = lock.newCondition(); condition.await(); // 类似 wait() condition.signal(); // 类似 notify()

示例:生产者-消费者模型中,可为空队列和满队列分别设置 Condition。

5. 中断与超时

  • synchronized
    • 线程等待锁时无法被中断。
    • 无超时机制,可能永久等待。
  • ReentrantLock
// 支持中断 lock.lockInterruptibly(); // 支持超时 if (lock.tryLock(1, TimeUnit.SECONDS)) { try { /* 操作 */ } finally { lock.unlock(); } }

6. 性能差异

  • JDK 1.5 时 ReentrantLock 性能显著优于 synchronized
  • JDK 1.6 后 JVM 对 synchronized 进行了大量优化(锁升级、自适应自旋等),两者性能差距缩小。
  • 在高竞争场景下,ReentrantLock 仍可能表现更稳定。

三、适用场景

优先使用 synchronized 的情况

  • 简单的同步场景,代码简洁性更重要。
  • 不需要高级功能(如条件变量、中断、超时)。
  • 资源竞争不激烈时,性能可接受。

优先使用 ReentrantLock 的情况

  • 需要公平锁、可中断锁、超时锁等高级功能。
  • 需要多个条件变量(如阻塞队列的实现)。
  • 需要跨方法加锁/释放锁(如:在方法 A 加锁,在方法 B 释放)。
  • 竞争激烈且性能要求高。

四、示例对比

场景:生产者-消费者模型

// 使用 synchronized(单一条件) public synchronized void put(Object item) throws InterruptedException { while (queue.isFull()) { wait(); // 只能在一个条件上等待 } queue.put(item); notifyAll(); } // 使用 ReentrantLock(多条件) private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); public void put(Object item) throws InterruptedException { lock.lock(); try { while (queue.isFull()) { notFull.await(); // 只在 "非满" 条件上等待 } queue.put(item); notEmpty.signal(); // 只唤醒等待 "非空" 的线程 } finally { lock.unlock(); } }

五、总结

  • synchronized 简单、安全、自动管理锁释放,适合大多数常规同步场景。
  • ReentrantLock 功能强大、灵活可控,适合复杂并发场景和高级需求。
  • 从 JDK 1.6 开始,两者性能接近,选择时应更关注功能需求代码可维护性
  • 在 JDK 后续版本中,synchronized 仍在持续优化(如锁消除、锁粗化等),而 ReentrantLock 提供了更细粒度的并发控制。

面试回答

首先,synchronized 是 Java 语言层面的关键字,是 JVM 原生支持的锁机制。它的使用非常简单,编译器会自动处理锁的获取和释放,所以基本不会因为忘记释放锁而导致死锁,易用性是它的最大优点
ReentrantLockJUC 包下的一个类,是 JDK 层面实现的锁。它需要开发者显式地调用 lock()unlock() 方法,通常在 finally 块中释放锁,否则容易出问题。所以从使用门槛上说,synchronized 更低。

在功能上,ReentrantLocksynchronized 灵活和强大得多,主要有三点:

  1. 可中断获取锁:当线程尝试获取 ReentrantLock 时,如果长时间拿不到,可以响应中断,通过 lockInterruptibly() 方法放弃等待去做别的事情。而 synchronized 在等待锁时,线程会一直阻塞,无法被中断。
  2. 公平锁选项ReentrantLock 可以在构造函数中指定是否是公平锁(先等待的线程先获得锁)。虽然公平锁性能有损耗,但能防止线程饥饿。synchronized 则是非公平的,谁抢到算谁的,性能通常更好。
  3. 条件变量(Condition):这是非常强大的一点。一个 ReentrantLock 可以创建多个 Condition 对象,用来实现更精细的线程等待/通知。比如,我们可以让一部分线程在条件A上等待,另一部分在条件B上等待,唤醒时也可以选择只唤醒等待条件A的线程。而 synchronized 只能配合 wait()notify(),所有线程都在同一个条件队列上,唤醒是随机的(notify)或全部唤醒(notifyAll),不够精确。

在早期版本(JDK 1.5 之前),ReentrantLock 的性能比 synchronized 好很多。但后来 JVM 对 synchronized 进行了大幅优化,比如引入了偏向锁、轻量级锁、自旋锁、锁消除、锁粗化等。所以在高版本的 JDK(如 1.8 及以后)中,两者在性能上已经相差无几,synchronized 甚至在一些常见场景下更优,因为它有 JVM 的持续优化。

所以,我的选择原则通常是:

  • 优先考虑synchronized:在满足需求的情况下,因为它简单、安全(自动释放),且性能不差。大部分标准的同步场景用它就够了。
  • 需要高级功能时再用 ReentrantLock:比如我需要用到可中断、公平锁,或者需要复杂的条件等待机制(典型应用就是“生产者-消费者”模型),这时 ReentrantLock 是唯一的选择。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

Read more

告别JDK版本地狱!Windows环境下Java版本管理终极攻略

告别JDK版本地狱!Windows环境下Java版本管理终极攻略

🎯 前言 作为Java开发者,你是否遇到过这些问题:项目A需要JDK 8,项目B需要JDK 11,项目C又要求JDK 17?频繁地卸载重装JDK不仅麻烦,还容易出错。在Windows系统中优雅地管理和切换多个JDK版本,是每个Java开发者必备的技能! 想象一下,你就像一个工具箱管理员,不同的项目需要不同的工具(JDK版本)。有了合适的管理方法,你可以随时拿出需要的工具,而不用每次都重新整理整个工具箱。 为什么需要多JDK版本管理? 1. 项目兼容性:不同项目可能依赖不同的JDK版本 2. 新特性体验:测试新版本JDK的特性和性能 3. 维护旧项目:保持对历史项目的支持 4. 开发效率:避免频繁安装卸载JDK的时间成本 本篇你将学到: * 多种JDK版本管理方案的优缺点对比 * 手动配置环境变量的详细步骤 * 使用批处理脚本快速切换JDK * JENV、SDKMAN等专业工具的使用 * IDE中的JDK配置和项目级别设置 * 常见问题的排查和解决方案 1. JDK版本管理方案对比 1.1 方案概览 在Windows系统中,管理多个JDK版本主要

By Ne0inhk
.NET到Java的终极迁移指南:最快转型路线图

.NET到Java的终极迁移指南:最快转型路线图

文章目录 * **导言:为何选择“最快路线”?** * **第一篇:战略准备篇——重塑思维,规划路径** * **1.1 核心理念差异:从CLR到JVM** * **1.2 技能映射与缺口分析** * **1.3 工具链瞬时切换** * **第二篇:战术执行篇——从代码到架构的快速穿越** * **2.1 语言层:C#到Java的思维转换(附代码对照)** * **2.2 核心技术栈:Spring Boot深度速成** * **2.3 项目实践:用“微转型”代替“大重构”** * **2.4 利用“桥梁”技术加速过渡(可选但高效)** * **第三篇:实战精进与避坑指南** * **3.1 从“

By Ne0inhk
【开题答辩全过程】以 基于java电脑售后服务管理系统设计为例,包含答辩的问题和答案

【开题答辩全过程】以 基于java电脑售后服务管理系统设计为例,包含答辩的问题和答案

个人简介 一名14年经验的资深毕设内行人,语言擅长Java、php、微信小程序、Python、Golang、安卓Android等 开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。 感谢大家的关注与支持! 各位老师好,我是xx同学,我的毕业设计题目是《基于Java电脑售后服务管理系统设计》。本系统旨在为电脑售后服务提供信息化管理解决方案,主要解决传统售后服务中存在的预约不便、进度查询困难、信息管理混乱等问题。 系统采用B/S架构,后端使用Java语言和Spring Boot框架开发,数据库采用MySQL,前端使用HTML、CSS、JavaScript配合Vue.js框架。系统主要包含三个角色:管理员负责用户管理、维修员管理、服务类型管理、预约服务管理、服务费用管理、服务记录管理、意见反馈管理、系统管理和订单管理;维修员可以管理个人信息、查看服务记录、处理预约任务和提交意见反馈;用户可以管理个人信息、

By Ne0inhk
Java 常见Exception全面解析:出现场景、错误排查与代码修正实战

Java 常见Exception全面解析:出现场景、错误排查与代码修正实战

文章目录 * 课程导言 * 适用对象 * 学习目标 * 课程安排 * 教学方式 * 第一部分:Java异常体系回顾(约10分钟) * 1.1 异常是什么? * 1.2 Java异常体系结构 * 1.3 异常信息解读 * 第二课时(上):运行时异常深度剖析(约30分钟) * 2.1 NullPointerException(空指针异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法流程图 * 代码修正与预防 * 2.2 ArrayIndexOutOfBoundsException(数组下标越界异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法 * 代码修正与预防 * 2.3 ClassCastException(类型转换异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法 * 代码修正与预防 * 2.

By Ne0inhk