1. 核心概念与定位

  • synchronized
    Java 内置的关键字,属于 JVM 层面的隐式锁。通过在方法或代码块上声明,自动实现锁的获取与释放,无需手动操作。设计目标是提供简单易用的基础同步能力,适合大多数常规同步场景。

  • ReentrantLock
    位于 java.util.concurrent.locks 包下的类,实现了 Lock 接口,属于显式锁。需要通过 lock() 手动获取锁,unlock() 手动释放锁(通常在 finally 块中执行)。设计目标是提供更灵活的同步控制,满足复杂场景需求。

2. 核心共性

  • 可重入性:两者都是可重入锁,即同一线程可以多次获取同一把锁(如递归调用同步方法),不会产生死锁。
  • 线程互斥:核心功能一致,都能保证同一时间只有一个线程进入临界区,实现线程安全。

3. 关键区别

   (1)锁的获取与释放方式
  • synchronized 是隐式锁,自动获取和释放,无需手动操作
  • ReentrantLock 是显式锁,必须通过 lock() 获取锁,unlock() 释放锁
  • ReentrantLock 必须在 finally 块中释放锁,否则可能因异常导致锁无法释放,造成死锁
  • synchronized 更简洁,ReentrantLock 更灵活但需更小心使用
// synchronized 示例
public class SynchronizedExample {private int count = 0;// 隐式获取和释放锁public synchronized void increment() {count++; // 临界区}
}// ReentrantLock 示例
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private int count = 0;private final ReentrantLock lock = new ReentrantLock();// 显式获取和释放锁public void increment() {lock.lock(); // 显式获取锁try {count++; // 临界区} finally {lock.unlock(); // 显式释放锁(必须在finally中)}}
}
   (2)尝试获取锁与超时机制

   ReentrantLock 支持超时获取锁

  • ReentrantLock 的 tryLock() 方法可以尝试获取锁而不阻塞,或设置超时时间
  • 超时机制可以避免线程无限期等待锁,提高系统的灵活性和稳定性

   synchronized 不支持超时机制

  • synchronized 一旦开始等待,就必须等到锁释放,无法主动放弃
// ReentrantLock 支持尝试获取锁和超时
public class LockWithTimeout {private final ReentrantLock lock = new ReentrantLock();public boolean tryDoSomething() throws InterruptedException {// 尝试获取锁,最多等待1秒if (lock.tryLock(1, TimeUnit.SECONDS)) {try {// 执行操作return true;} finally {lock.unlock();}}return false; // 获取锁失败}
}// synchronized 不支持超时,必须一直等待
public class SynchronizedNoTimeout {public synchronized void doSomething() {// 无法设置超时,必须等待锁释放}
}
  (3)可中断特性

   ReentrantLock 支持中断

  • ReentrantLock 的 lockInterruptibly() 方法允许线程在等待锁的过程中响应中断
  • 可中断特性在需要取消任务或关闭服务时非常有用

   synchronized 不支持中断

  • synchronized 等待锁的过程无法被中断,可能导致线程一直阻塞
// ReentrantLock 支持中断
public class InterruptibleLock {private final ReentrantLock lock = new ReentrantLock();public void doWithInterrupt() throws InterruptedException {// 可被中断的锁获取lock.lockInterruptibly();try {// 执行操作} finally {lock.unlock();}}
}// synchronized 不支持中断
public class SynchronizedNotInterruptible {public synchronized void doSomething() {// 即使线程被中断,仍会继续等待锁}
}
(4)公平锁实现

什么是 “公平锁” 与 “非公平锁”:

  • 公平锁:线程获取锁的顺序严格遵循 “请求锁的先后顺序”(FIFO),先请求的线程一定先拿到锁,不会出现 “后请求的线程插队” 的情况。
  • 非公平锁:线程获取锁时不严格遵循请求顺序,允许 “插队”—— 即使队列中有等待的线程,新到达的线程也可以尝试直接抢占锁,抢占失败后再进入队列排队。

ReentrantLock 可实现公平锁

  • ReentrantLock 可以通过构造函数参数 true 创建公平锁
  • 公平锁保证线程获取锁的顺序与请求顺序一致,避免线程饥饿
  • 非公平锁允许线程 "插队" 获取锁,可能导致某些线程长时间等待,但性能更高

synchronized 只能是非公平锁

  • synchronized 始终是非公平锁,无法改为公平锁
// ReentrantLock 可创建公平锁
public class FairLockExample {// 公平锁:按请求顺序获取锁private final ReentrantLock fairLock = new ReentrantLock(true);public void doWithFairLock() {fairLock.lock();try {// 执行操作} finally {fairLock.unlock();}}
}// synchronized 只能是非公平锁
public class SynchronizedNonFair {// 无法设置为公平锁,始终是非公平的public synchronized void doSomething() {// 执行操作}
}

为什么synchronized 非公平,不支持公平锁?:

synchronized 的实现依赖 JVM 底层的 对象监视器(Monitor),其锁分配逻辑本质是 “优先让当前可执行的线程获取锁,减少线程切换开销”,具体体现在两个关键场景:

场景 1:新线程请求锁时,直接 “插队” 抢占

当一个线程 A 释放锁时,JVM 并不会立刻唤醒等待队列中最前面的线程 B(公平锁逻辑),而是会先检查 当前是否有新线程 C 正在请求锁
如果有新线程 C,JVM 会允许 C 直接抢占锁(无需进入等待队列),只有当没有新线程时,才会唤醒队列中的 B。

为什么这么做?
线程从 “等待状态” 被唤醒,需要经历 内核态→用户态 的切换(操作系统级操作),这个过程开销较大;而新线程 C 本身处于 “运行态”,直接让它获取锁可以避免一次线程切换,显著提升性能。

例如:

  • 线程 A 释放锁时,等待队列中有线程 B(已等待 10ms);
  • 此时线程 C 刚执行到 synchronized 代码块,请求锁;
  • JVM 会让 C 直接拿到锁,B 继续等待;
  • 只有当 A 释放锁时没有新线程,才唤醒 B。

场景 2:线程重入时,无需排队

synchronized 是 可重入锁(同一线程可多次获取同一把锁),而重入逻辑本身就是 “非公平” 的 —— 线程再次请求已持有的锁时,无需进入等待队列,直接成功获取。

这是因为:线程持有锁时,本身就有权限访问临界区,重入时跳过排队是合理的,且能避免 “自己等自己” 的死锁问题。但从公平性角度看,这相当于 “持有锁的线程插队”,优先于队列中的其他线程。

例如:

  • 线程 A 已持有锁,执行到一个嵌套的 synchronized 代码块;
  • 此时等待队列中有线程 B;
  • 线程 A 无需排队,直接重入锁,B 继续等待。

公平性的性能代价:

代价 1:强制线程切换,增加开销

公平锁要求严格按 “请求顺序” 分配锁,这意味着:

  • 当锁释放时,必须唤醒等待队列中最前面的线程(不能让新线程插队);
  • 被唤醒的线程需要从 “等待态” 切换到 “运行态”,这个过程涉及操作系统内核操作,开销远大于 “新线程直接抢占”。

代价 2:锁竞争激烈时,吞吐量下降

公平锁会导致 “等待队列越长,新线程越难获取锁”,即使新线程能快速执行完临界区,也必须排队。

例如:

  • 等待队列中有 10 个线程,每个线程执行临界区需要 100ms;
  • 此时有一个新线程,临界区仅需 1ms;
  • 公平锁下,新线程必须排在第 11 位,等待 10×100ms=1000ms 后才能执行,总耗时 1001ms;
  • 非公平锁下,新线程可以直接抢占,总耗时 1ms(新线程)+ 10×100ms(队列线程)= 1001ms?不 —— 实际是新线程执行 1ms 后释放锁,队列中的第一个线程立刻执行,总耗时会更短(1ms + 100ms + ...),因为减少了一次线程切换的等待。

4.总结

ReentrantLock 和 synchronized 的核心区别可以概括为:

特性ReentrantLocksynchronized
锁操作方式显式(lock/unlock)隐式(自动获取释放)
灵活性高,支持多种获取方式低,固定的获取方式
超时获取支持不支持
可中断性支持不支持
公平性可选择仅非公平
锁状态查询可查询(isLocked () 等)不可查询
使用复杂度较高,需手动释放低,不易出错

synchronized 是 “简单易用的基础方案”,ReentrantLock 是 “灵活可控的高级方案”。现代 Java 版本中两者性能差异不大,选择时主要依据功能需求:简单场景用 synchronized,复杂场景用 ReentrantLock。

适用场景:

  • 优先用 synchronized
    简单同步场景(如普通方法 / 代码块同步)、追求代码简洁性、低并发场景。
    优势:无需手动释放锁,减少出错概率,JVM 持续优化(如偏向锁、轻量级锁)。

  • 优先用 ReentrantLock
    需要超时获取锁、可中断锁、公平锁的场景;需要多个条件变量精确控制线程唤醒;复杂同步逻辑(如生产者 - 消费者模型的精细化控制)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98816.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98816.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/98816.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【npm】npm 包更新工具 npm-check-updates (ncu)

npm 包太多了,一个项目有那么多依赖包,它们的升级管理需要一个工具:npm-check-updates: 安装: npm install -g npm-check-updates安装之后,就可以使用它的命令:ncu 查看哪些包可以升级&#xff…

go资深之路笔记(一) Context

一、 Context 的正确使用与底层原理 1.结构体 type Context interface {// Deadline 返回此 Context 被取消的时间点。// 如果未设置截止时间,ok 为 false。Deadline() (deadline time.Time, ok bool)// Done 返回一个 channel。当 Context 被取消或超时后&#xff…

VS2022 + Qt5.9 中文乱码/项目设置utf-8编码

🛠️ 解决QT5.9 VS2022中文乱码的全面方案 📁 1. 检查文件编码与编译器设置 确保源文件是 带BOM的UTF-8 编码对MSVC编译器很重要。VS2022默认可能使用本地编码(如GB2312)解析源文件,即使文件以UTF-8保存。 查看和设置…

数据库--MySQL数据管理

数据库–MySQL数据管理 文章目录数据库--MySQL数据管理1.外键管理2.数据库数据管理3.DML语言3.1添加数据3.2修改数据3.3删除数据4.练习1.外键管理 外键概念 如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键。由此可见,…

【C++练习】13.C++输出九九乘法表的方法详解

目录 C++输出九九乘法表的方法详解 方法1:双重for循环(最基础) 思考: 代码分析: 特点: 方法2:使用while循环 思考: 代码分析: 特点: 方法3:使用递归实现 思考: 代码分析: 特点: 方法4:格式化输出(对齐美观) 思考: 代码分析: 特点: 方法5:使用函数封装 思考…

MVC及其衍生

MVC 把软件分成模型(Model)、视图(View)、控制器(Controller)三个基本部分。 事实上对应着 Controller——输入 用户交互,将输入处理成Controller能处理的形式 Model——处理 描述状态、逻辑规律…

微硕WINSOK MOS管WSF3089,赋能汽车转向系统安全升级

随着汽车电子化程度不断提高,转向系统对高效功率器件的需求日益增长。微硕WINSOK推出的N沟道Trench MOS管WSF3089,以30 V/72 A大电流、4.5 mΩ超低导通电阻和TO-252-2L紧凑封装,为EPS(电动助力转向)电机驱动、电源管理…

淘宝拍立淘接口的接入与应用||item_search_img-按图搜索淘宝商品(拍立淘)

淘宝拍立淘接口的接入与应用如下:接入流程注册与认证:开发者账号注册:访问淘宝开放平台,进行开发者账号注册。创建应用:在控制台创建新应用,获取 App Key 和 App Secret,这是接口调用的凭证。申…

Python学习-day8 元组tuple

元组(Tuple)是Python中一种不可变的序列类型,用于存储多个有序元素。与列表(List)类似,但元组一旦创建后不能修改(不可添加、删除或修改元素),这使得它在安全性、性能优化…

大数据毕业设计选题推荐-基于大数据的国家医用消耗选品采集数据可视化分析系统-Hadoop-Spark-数据可视化-BigData

✨作者主页:IT毕设梦工厂✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、PHP、.NET、Node.js、GO、微信小程序、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇…

二次学习C语言补充2

文章目录表栈、队列、二叉树一、二叉树二、表栈三、队列链表一、单向链表二、循环链表、双向链表和双向循环链表预处理一、预处理二、宏定义文件文件操作补充本篇文章是对二次学习C语言12——文件操作 二次学习C语言14——预处理及模块化 二次学习C语言15——链表 二次学习C语言…

2.9Vue创建项目(组件)的补充

1.再创建和引入vue的选择2.VsCode插件 安装Vue自己搜索最新的3.style自己的作用域在一个组件中引入另一个文件的子组件,给当前组件设置样式,那么子组件的样式也会改变的。为了解决这个问题 我们在自己的style中设置一个属性4.另一种创建vue 的方式(主流…

算法高频题

刷题:LeetCode(Top 100-150题,至少刷两遍)。重点:链表、树、二分查找、动态规划、回溯、栈/队列。 每一个题型,前10个高频题 算法思考框架参考:算法题思维框架-CSDN博客 高频顺序参考网站&…

服务器安装 LDOPE(MODIS 数据处理工具)

目录下载方式1-(简单快捷)根据WRF-VPRM 需要打补丁下载方式2:(手动安装依赖)一、安装所需依赖库(4 个主库 2 个基础库)另- HDF-EOS 手动编译二、解压并安装 LDOPE参考下载方式1-(简…

克隆代币 + 捆绑开盘:多链环境下的低成本发币玩法

在加密世界,发币已经不再是“少数开发者的专利”。随着工具的普及,任何人都可以快速发行一个在加密世界,发币已经不再是“少数开发者的专利”。随着工具的普及,任何人都可以快速发行一个代币。但问题是:如何在保证低成…

数据结构中的 二叉树

1.前言 在 Java 中,树(Tree)是一种非线性数据结构,由节点(Node)组成,常见的线性表则是我们之前学过的顺序表、链表、栈、队列等等。每个节点包含数据和指向子节点的引用。树的常见实现方式包括二…

IntelliJ IDEA 启动项目时配置端口指南

🌟 一、为什么需要手动设置启动端口? 默认情况下,Spring Boot 应用会使用 8080 端口启动。但在以下场景中,我们必须自定义端口: 多个微服务同时运行,需避免端口冲突;团队协作开发,统…

spark sql之from_json函数

目录前言函数语法参数说明返回值案例案例1案例2前言 在Spark SQL中,from_json函数用于解析包含JSON字符串的列,并将其转换为Spark SQL的结构化类型(如struct、map或array) 函数语法 from_json(jsonStr, schema [, options])参数…

数据结构 之 【位图的简介】

目录 1.位图的引入 2.位图概念 3.位图的实现 3.1前提准备 3.2set 3.3reset 3.4test 4.位图的应用 1.位图的引入 给40亿个不重复的无符号整数,没排过序 再给一个无符号整数,如何快速判断这个无符号整数是否在 这40亿个数中 首先,一个…

[iOS] ViewController 的生命周期

文章目录前言一、UIViewController 生命周期有关函数二、UIViewController 中函数的执行顺序运行结果1.present和dismiss2.push和pop三、总结前言 UIViewController 是在 iOS 开发中一个非常重要的角色,他是 view 和 model 的桥梁,通过 UIViewControlle…