锁的艺术:深入浅出讲解乐观锁与悲观锁
在多线程和分布式系统中,数据一致性是一个核心问题。锁机制作为解决并发冲突的重要手段,被广泛应用于各种场景。乐观锁和悲观锁是两种常见的锁策略,它们在设计理念、实现方式和适用场景上各有特点。本文将深入探讨乐观锁和悲观锁的原理、实现、优缺点以及具体的应用实例,并结合代码进行详细讲解,帮助读者更好地理解和应用这两种锁机制。
目录
一、锁的基本概念
二、悲观锁
(一)悲观锁的基本概念
(二)悲观锁的特点
(三)悲观锁的实现方式
1. 数据库中的悲观锁
2. Java中的悲观锁
(四)悲观锁的优缺点
三、乐观锁
(一)乐观锁的基本概念
(二)乐观锁的特点
(三)乐观锁的实现方式
1. 基于版本号的乐观锁
2. 基于时间戳的乐观锁
(四)乐观锁的优缺点
四、乐观锁与悲观锁的对比
(一)锁机制
(二)性能
(三)适用场景
五、总结
一、锁的基本概念
在并发编程中,锁是一种用于控制多个线程对共享资源访问的机制。锁的主要目的是确保在同一时间只有一个线程能够访问共享资源,从而避免数据竞争和不一致问题。锁的实现方式多种多样,但其核心思想是通过某种机制来限制对共享资源的并发访问。
二、悲观锁
(一)悲观锁的基本概念
悲观锁是一种基于“悲观”假设的锁机制。它认为在并发环境中,多个线程对共享资源的访问很可能会发生冲突,因此在访问共享资源之前,会先对资源进行加锁。只有获得锁的线程才能访问资源,其他线程必须等待锁释放后才能继续执行。悲观锁的核心思想是“宁可错杀一千,不可放过一个”,通过严格的锁机制来保证数据的一致性。
(二)悲观锁的特点
- 强一致性:悲观锁通过加锁机制严格限制对共享资源的并发访问,能够确保在任何时候只有一个线程能够修改资源,从而保证数据的强一致性。
- 高安全性:由于悲观锁在访问资源之前会先加锁,因此可以有效避免数据竞争和并发冲突,适用于对数据一致性要求较高的场景。
- 性能瓶颈:悲观锁的加锁和解锁操作会增加系统开销,尤其是在高并发场景下,锁的争用可能导致线程阻塞,降低系统的性能。
- 适用场景:悲观锁适用于写操作较多、数据竞争激烈的场景,例如数据库事务中的行锁和表锁。
(三)悲观锁的实现方式
悲观锁可以通过多种方式实现,常见的有基于数据库的锁机制和基于Java同步原语的锁机制。
1. 数据库中的悲观锁
在数据库中,悲观锁可以通过SELECT ... FOR UPDATE
语句实现。该语句会在查询数据时对数据行加锁,其他事务必须等待锁释放后才能对该行数据进行操作。
-- 查询并锁定一行数据
SELECT * FROM users WHERE id = 1 FOR UPDATE;
FOR UPDATE
:该子句的作用是锁定查询结果中的行,防止其他事务对该行数据进行修改。
在Java中,可以通过JDBC操作数据库来实现悲观锁。以下是一个简单的示例代码:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class PessimisticLockExample {public static void main(String[] args) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {// 获取数据库连接connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");// 设置事务为非自动提交connection.setAutoCommit(false);// 查询并锁定一行数据String sql = "SELECT * FROM users WHERE id = ? FOR UPDATE";preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1, 1);resultSet = preparedStatement.executeQuery();if (resultSet.next()) {// 获取锁定的数据String name = resultSet.getString("name");System.out.println("Locked user: " + name);// 模拟业务逻辑处理Thread.sleep(5000);// 更新数据String updateSql = "UPDATE users SET name = ? WHERE id = ?";preparedStatement = connection.prepareStatement(updateSql);preparedStatement.setString(1, "New Name");preparedStatement.setInt(2, 1);preparedStatement.executeUpdate();// 提交事务connection.commit();}} catch (SQLException | InterruptedException e) {e.printStackTrace();try {// 回滚事务if (connection != null) {connection.rollback();}} catch (SQLException ex) {ex.printStackTrace();}} finally {// 关闭资源try {if (resultSet != null) {resultSet.close();}if (preparedStatement != null) {preparedStatement.close();}if (connection != null) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}}
}
代码说明:
-
使用
SELECT ... FOR UPDATE
语句查询并锁定数据行。 -
设置事务为非自动提交模式,确保在事务提交之前,其他事务无法对该行数据进行修改。
-
在锁定数据后,模拟业务逻辑处理(如
Thread.sleep(5000)
),然后更新数据并提交事务。 -
如果发生异常,回滚事务并释放资源。
2. Java中的悲观锁
在Java中,悲观锁可以通过java.util.concurrent.locks
包中的Lock
接口及其实现类(如ReentrantLock
)来实现。ReentrantLock
提供了比内置锁(synchronized
)更灵活的锁操作,例如尝试锁定(tryLock
)、设置超时时间(tryLock(long timeout, TimeUnit unit)
)等。
以下是一个使用ReentrantLock
实现悲观锁的示例代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private final Lock lock = new ReentrantLock();public void doSomething() {lock.lock(); // 加锁try {// 模拟业务逻辑System.out.println("Thread " + Thread.currentThread().getName() + " is doing something.");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock(); // 释放锁}}public static void main(String[] args) {ReentrantLockExample example = new ReentrantLockExample();// 创建多个线程访问共享资源Thread t1 = new Thread(example::doSomething, "Thread-1");Thread t2 = new Thread(example::doSomething, "Thread-2");t1.start();t2.start();}
}
代码说明:
-
使用
ReentrantLock
的lock()
方法加锁,unlock()
方法释放锁。 -
在
try
块中执行业务逻辑,确保在异常情况下能够通过finally
块释放锁。 -
多个线程访问共享资源时,只有获得锁的线程能够执行
doSomething
方法,其他线程必须等待锁释放。
(四)悲观锁的优缺点
优点
- 数据一致性高:悲观锁通过严格的锁机制确保数据的一致性,适用于对数据一致性要求较高的场景。
- 实现简单:悲观锁的实现相对简单,尤其是在数据库层面,通过
SELECT ... FOR UPDATE
语句即可实现。
缺点
- 性能瓶颈:悲观锁的加锁和解锁操作会增加系统开销,尤其是在高并发场景下,锁的争用可能导致线程阻塞,降低系统的性能。
- 资源利用率低:由于悲观锁限制了并发访问,可能导致资源利用率较低,尤其是在读操作较多的场景下。
三、乐观锁
(一)乐观锁的基本概念
乐观锁是一种基于“乐观”假设的锁机制。它认为在并发环境中,多个线程对共享资源的访问发生冲突的概率较低,因此在访问资源时不加锁,而是通过其他机制(如版本号或时间戳)来检测数据是否被其他线程修改。如果检测到数据被修改,则放弃当前操作并重试。乐观锁的核心思想是“先做事,再检查”,通过减少锁的使用来提高系统性能。
(二)乐观锁的特点
- 高性能:乐观锁减少了锁的使用,降低了锁的开销,适用于读操作较多、写操作较少的场景,能够显著提高系统的性能。
- 资源利用率高:乐观锁允许多个线程并发访问共享资源,提高了资源的利用率。
- 实现复杂:乐观锁的实现相对复杂,需要通过版本号或时间戳等机制检测数据是否被修改。
- 适用场景:乐观锁适用于读操作较多、写操作较少的场景,例如缓存系统、分布式系统中的数据一致性控制。
(三)乐观锁的实现方式
乐观锁可以通过版本号(Version Number)或时间戳(Timestamp)来实现。以下分别介绍这两种实现方式。
1. 基于版本号的乐观锁
基于版本号的乐观锁通过为每个数据项添加一个版本号字段来实现。每次修改数据时,版本号加1。在更新数据时,会检查版本号是否发生变化。如果版本号发生变化,说明数据被其他线程修改过,当前操作需要重试。以下是一个基于版本号的乐观锁的实现示例:
import java.util.concurrent.atomic.AtomicInteger;public class OptimisticLockExample {private int value; // 数据值private AtomicInteger version = new AtomicInteger(0); // 版本号public void updateValue(int newValue) {int currentVersion = version.get(); // 获取当前版本号while (true) {// 检查版本号是否发生变化if (version.compareAndSet(currentVersion, currentVersion + 1)) {// 如果版本号未发生变化,更新数据value = newValue;System.out.println("Updated value to " + newValue + " with version " + version.get());break;} else {// 如果版本号发生变化,重试currentVersion = version.get();System.out.println("Version changed, retrying...");}}}public static void main(String[] args) {OptimisticLockExample example = new OptimisticLockExample();// 创建多个线程更新数据Thread t1 = new Thread(() -> example.updateValue(10), "Thread-1");Thread t2 = new Thread(() -> example.updateValue(20), "Thread-2");t1.start();t2.start();}
}
代码说明:
-
使用
AtomicInteger
来实现版本号的线程安全操作。 -
在更新数据时,通过
compareAndSet
方法检查版本号是否发生变化。如果版本号未发生变化,则更新数据并增加版本号;如果版本号发生变化,则重试。 -
多个线程更新数据时,通过版本号机制避免冲突。
2. 基于时间戳的乐观锁
基于时间戳的乐观锁通过为每个数据项添加一个时间戳字段来实现。每次修改数据时,更新时间戳。在更新数据时,会检查时间戳是否发生变化。如果时间戳发生变化,说明数据被其他线程修改过,当前操作需要重试。以下是一个基于时间戳的乐观锁的实现示例:
import java.util.concurrent.atomic.AtomicLong;public class OptimisticLockWithTimestamp {private int value; // 数据值private AtomicLong timestamp = new AtomicLong(System.currentTimeMillis()); // 时间戳public void updateValue(int newValue) {long currentTimestamp = timestamp.get(); // 获取当前时间戳while (true) {// 检查时间戳是否发生变化if (timestamp.compareAndSet(currentTimestamp, System.currentTimeMillis())) {// 如果时间戳未发生变化,更新数据value = newValue;System.out.println("Updated value to " + newValue + " with timestamp " + timestamp.get());break;} else {// 如果时间戳发生变化,重试currentTimestamp = timestamp.get();System.out.println("Timestamp changed, retrying...");}}}public static void main(String[] args) {OptimisticLockWithTimestamp example = new OptimisticLockWithTimestamp();// 创建多个线程更新数据Thread t1 = new Thread(() -> example.updateValue(10), "Thread-1");Thread t2 = new Thread(() -> example.updateValue(20), "Thread-2");t1.start();t2.start();}
}
代码说明:
-
使用
AtomicLong
来实现时间戳的线程安全操作。 -
在更新数据时,通过
compareAndSet
方法检查时间戳是否发生变化。如果时间戳未发生变化,则更新数据并更新时间戳;如果时间戳发生变化,则重试。 -
多个线程更新数据时,通过时间戳机制避免冲突。
(四)乐观锁的优缺点
优点
- 高性能:乐观锁减少了锁的使用,降低了锁的开销,适用于读操作较多、写操作较少的场景,能够显著提高系统的性能。
- 资源利用率高:乐观锁允许多个线程并发访问共享资源,提高了资源的利用率。
- 减少锁竞争:乐观锁通过版本号或时间戳机制避免了锁的竞争,减少了线程阻塞的可能性。
缺点
- 实现复杂:乐观锁的实现相对复杂,需要通过版本号或时间戳等机制来检测数据是否被修改。
- 冲突重试机制:乐观锁在检测到冲突时需要重试,可能会导致操作失败或性能下降,尤其是在高并发写操作较多的场景下。
- 适用场景有限:乐观锁适用于读操作较多、写操作较少的场景,对于写操作较多的场景,其性能优势可能不明显。
四、乐观锁与悲观锁的对比
(一)锁机制
-
悲观锁:通过加锁机制限制对共享资源的并发访问,确保在同一时间只有一个线程能够访问共享资源。
-
乐观锁:不加锁,通过版本号或时间戳机制检测数据是否被修改,如果检测到冲突则重试。
(二)性能
-
悲观锁:加锁和解锁操作会增加系统开销,尤其是在高并发场景下,锁的争用可能导致线程阻塞,降低系统的性能。
-
乐观锁:减少了锁的使用,降低了锁的开销,适用于读操作较多、写操作较少的场景,能够显著提高系统的性能。
(三)适用场景
-
悲观锁:适用于写操作较多、数据竞争激烈的场景,例如数据库事务中的行锁和表锁。
-
乐观锁:适用于读操作较多、写操作较少的场景,例如缓存系统、分布式系统中的数据一致性控制。
五、总结
乐观锁 | 悲观锁 | |
---|---|---|
核心思想 | 假设冲突较少,先操作再检查冲突,通过版本号或时间戳检测数据是否被修改。 | 假设冲突较多,通过加锁机制限制对共享资源的并发访问。 |
锁机制 | 不加锁,通过版本号或时间戳检测数据是否被修改。 | 加锁,通过锁机制限制对共享资源的并发访问。 |
性能 | 读操作多、写操作少时性能高,减少锁的开销。 | 写操作多时性能可能受限,锁的争用可能导致线程阻塞。 |
资源利用率 | 允许多个线程并发访问,资源利用率高。 | 同一时间只有一个线程能访问资源,资源利用率低。 |
实现复杂度 | 实现相对复杂,需要版本号或时间戳机制。 | 实现相对简单,直接通过锁机制实现。 |
适用场景 | 读操作多、写操作少的场景,如缓存系统、分布式系统中的数据一致性控制。 | 写操作多、数据竞争激烈的场景,如数据库事务中的行锁和表锁。 |
冲突处理 | 发现冲突时重试操作。 | 通过锁机制避免冲突,其他线程等待锁释放。 |
数据一致性 | 数据一致性依赖于重试机制,可能需要多次尝试。 | 数据一致性高,通过锁机制严格保证。 |
并发能力 | 并发能力强,允许多个线程同时读取。 | 并发能力弱,同一时间只有一个线程能操作。 |
适用语言/框架 | Java中可通过Atomic 类实现版本号机制;数据库中可通过版本号字段实现。 | Java中可通过synchronized 或ReentrantLock 实现;数据库中可通过FOR UPDATE 实现。 |
优点 | 性能高、资源利用率高、减少锁竞争。 | 数据一致性高、实现简单、安全性高。 |
缺点 | 实现复杂、冲突时需要重试、适用场景有限。 | 性能瓶颈、资源利用率低、锁竞争可能导致线程阻塞。 |
乐观锁和悲观锁是两种常见的锁机制,它们在设计理念、实现方式和适用场景上各有特点。悲观锁通过加锁机制严格限制对共享资源的并发访问,能够确保数据的一致性,但可能会导致性能瓶颈。乐观锁通过版本号或时间戳机制检测数据是否被修改,减少了锁的使用,提高了系统的性能,但实现相对复杂,且在高并发写操作较多的场景下可能不适用。
在实际应用中,选择乐观锁还是悲观锁需要根据具体的业务场景和性能需求来决定。对于写操作较多、数据竞争激烈的场景,悲观锁可能是更好的选择;而对于读操作较多、写操作较少的场景,乐观锁则能够显著提高系统的性能。
通过本文的介绍,读者可以更好地理解乐观锁和悲观锁的原理、实现和应用,从而在实际开发中合理选择锁机制,优化系统的性能和可靠性。
相关文章:
锁的艺术:深入浅出讲解乐观锁与悲观锁
在多线程和分布式系统中,数据一致性是一个核心问题。锁机制作为解决并发冲突的重要手段,被广泛应用于各种场景。乐观锁和悲观锁是两种常见的锁策略,它们在设计理念、实现方式和适用场景上各有特点。本文将深入探讨乐观锁和悲观锁的原理、实现…...
在网页加载时自动运行js的方法(2025最新)
在网页加载时自动运行JavaScript方法有多种实现方式,以下是常见的几种方法: 1. 使用 DOMContentLoaded 事件 当初始HTML文档完全加载和解析后触发(无需等待图片等资源加载): document.addEventListener(DOMC…...

在Windows下编译出llama_cpp_python的DLL后,在虚拟环境中使用方法
定位编译生成的文件 在VS2022编译完成后,在构建目录(如build/Release或build/Debug)中寻找以下关键文件: ggml.dll、ggml_base.dll、ggml_cpu.dll、ggml_cuda.dll、llama.dll(核心动态链接库) llama_cp…...
CSS radial-gradient函数详解
目录 基本语法 关键参数详解 1. 渐变形状(Shape) 2. 渐变大小(Size) 3. 中心点位置(Position) 4. 颜色断点(Color Stops) 常见应用场景 1. 基本圆形渐变 2. 椭圆渐变 3. 模…...
n8n 自动化平台 Docker 部署教程(附 PostgreSQL 与更新指南)
n8n 自动化平台 Docker 部署教程(附 PostgreSQL 与更新指南) n8n 是一个强大的可视化工作流自动化工具,支持无代码或低代码地集成各种服务。本文将手把手教你如何通过 Docker 快速部署 n8n,并介绍如何使用 PostgreSQL、设置时区以…...

关于datetime获取时间的问题
import datetime print(datetime.now())如果用上述代码,会报错: 以下才是正确代码: from datetime import datetime print(datetime.now()) 结果: 如果想格式化时间,使用代码: from datetime import da…...
前端面试五之vue2基础
1.属性绑定v-bind(:) v-bind 是 Vue 2 中用于动态绑定属性的核心指令,它支持多种语法和用法,能够灵活地绑定 DOM 属性、组件 prop,甚至动态属性名。通过 v-bind,可以实现数据与视图之间的高效同…...
使用python实现奔跑的线条效果
效果,展示(视频效果展示): 奔跑的线条 from turtle import * import time t1Turtle() t2Turtle() t3Turtle() t1.hideturtle() t2.hideturtle() t3.hideturtle() t1.pencolor("red") t2.pencolor("green") t3…...
Oracle 审计参数:AUDIT_TRAIL 和 AUDIT_SYS_OPERATIONS
Oracle 审计参数:AUDIT_TRAIL 和 AUDIT_SYS_OPERATIONS 一 AUDIT_TRAIL 参数 1.1 参数功能 AUDIT_TRAIL 是 Oracle 数据库中最核心的审计控制参数,决定审计记录的存储位置和记录方式。 1.2 参数取值及含义 取值说明适用场景NONE禁用数据库审计测试环…...
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
一、为什么布局性能如此重要? 在Android应用中,布局渲染耗时直接决定了界面的流畅度。根据Google官方数据,超过60%的卡顿问题源于布局性能不佳。本文将彻底解析三大传统布局的性能奥秘,并提供可直接落地的优化方案。 二、三大布局…...

Unity版本使用情况统计(更新至2025年5月)
UWA发布|本期UWA发布的内容是Unity版本使用统计(第十六期),统计周期为2024年11月至2025年5月,数据来源于UWA网站(www.uwa4d.com)性能诊断提测的项目。希望给Unity开发者提供相关的行业趋势作为参…...

GPUCUDA 发展编年史:从 3D 渲染到 AI 大模型时代(上)
目录 文章目录 目录1960s~1999:GPU 的诞生:光栅化(Rasterization)3D 渲染算法的硬件化实现之路 学术界算法研究历程工业界产品研发历程光栅化技术原理光栅化技术的软件实现:OpenGL 3D 渲染管线设计 1. 顶点处理&…...

人机融合智能 | 可穿戴计算设备的多模态交互
可穿戴计算设备可以对人体以及周围环境进行连续感知和计算,为用户提供随时随地的智能交互服务。本章主要介绍人机智能交互领域中可穿戴计算设备的多模态交互,阐述以人为中心的智能穿戴交互设计目标和原则,为可穿戴技术和智能穿戴交互技术的设计提供指导,进而简述支持智能穿戴交…...

Impromptu VLA:用于驾驶视觉-语言-动作模型的开放权重和开放数据
25年5月来自清华和博世的论文“Impromptu VLA: Open Weights and Open Data for Driving Vision-Language-Action Models”。 用于自动驾驶的“视觉-语言-动作” (VLA) 模型前景光明,但在非结构化极端场景下却表现不佳,这主要是由于缺乏有针对性的基准测…...

AI智能体,为美业后端供应链注入“智慧因子”(4/6)
摘要:本文深入剖析美业后端供应链现状,其产品具有多样性、更新换代快等特点,原料供应和生产环节也面临诸多挑战。AI 智能体的登场为美业后端供应链带来变革,包括精准需求预测、智能化库存管理、优化生产计划排程、升级供应商管理等…...

跨平台资源下载工具:res-downloader 的使用体验
一款基于 Go Wails 的跨平台资源下载工具,简洁易用,支持多种资源嗅探与下载。res-downloader 一款开源免费的下载软件(开源无毒、放心使用)!支持Win10、Win11、Mac系统.支持视频、音频、图片、m3u8等网络资源下载.支持视频号、小程序、抖音、…...
ps蒙版介绍
一、蒙版的类型 Photoshop中有多种蒙版类型,每种适用于不同的场景: 图层蒙版(Layer Mask) 作用:控制图层的可见性,黑色隐藏、白色显示、灰色半透明。特点:可随时编辑,适合精细调整。…...

数据湖是什么?数据湖和数据仓库的区别是什么?
目录 一、数据湖是什么 (一)数据湖的定义 (二)数据湖的特点 二、数据仓库是什么 (一)数据仓库的定义 (二)数据仓库的特点 三、数据湖和数据仓库的区别 (一&#…...
用Ai学习wxWidgets笔记——在 VS Code 中使用 CMake 搭建 wxWidgets 开发工程
声明:本文整理筛选Ai工具生成的内容辅助写作,仅供参考 >> 在 VS Code 中使用 CMake 搭建 wxWidgets 开发工程 下面是一步步指导如何在 VS Code 中配置 wxWidgets 开发环境,包括跨平台设置(Windows 和 Linux)。…...

【深度学习新浪潮】如何入门三维重建?
入门三维重建算法技术需要结合数学基础、计算机视觉理论、编程实践和项目经验,以下是系统的学习路径和建议: 一、基础知识储备 1. 数学基础 线性代数:矩阵运算、向量空间、特征分解(用于相机矩阵、变换矩阵推导)。几何基础:三维几何(点、线、面的表示)、射影几何(单…...
Android实现点击Notification通知栏,跳转指定activity页面
效果 1、点击通知栏通知,假如app正在运行,则直接跳转到指定activity显示具体内容,在指定activity中按返回键返回其上一级页面。 2、点击通知栏通知,假如app已经退出,先从SplashActivity进入,显示app启动界…...

Codeforces Round 1025 (Div. 2) B. Slice to Survive
Codeforces Round 1025 (Div. 2) B. Slice to Survive 题目 Duelists Mouf and Fouad enter the arena, which is an n m n \times m nm grid! Fouad’s monster starts at cell ( a , b ) (a, b) (a,b), where rows are numbered 1 1 1 to n n n and columns 1 1 1 t…...

ubuntu中使用docker
上一篇我已经下载了一个ubuntu:20.04的镜像; 1. 查看所有镜像 sudo docker images 2. 基于本地存在的ubuntu:20.04镜像创建一个容器,容器的名为cppubuntu-1。创建的时候就会启动容器。 sudo docker run -itd --name cppubuntu-1 ubuntu:20.04 结果出…...
复制与图片文件同名的标签文件到目标路径
引言:在数据集构建中,我们经常需要挑选一些特殊类型的图片(如:零件中有特殊脏污背景的图片,写论文的时候想单独对这类情况进行热力图验证)。我们把挑选出来的图片放到一个文件夹下,这时候我想快…...
【深度学习-Day 24】过拟合与欠拟合:深入解析模型泛化能力的核心挑战
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...

[ElasticSearch] DSL查询
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...

iview中的table组件点击一行中的任意一点选中本行
<Table border ref"selection" size"small" on-row-click"onClickRow"></Table>// table组件点击一行任意位置选中onClickRow(row, index) {this.$refs.selection.toggleSelect(index)}写上toggleSelect(index)方法即可,…...

《探秘跨网段局域网IP广播:解锁网络通信的新姿势》
一、从基础出发:广播与跨网段 在计算机网络的世界中,广播域是一个至关重要的概念。简单来说,广播域是指网络中能接收任一台主机发出的广播帧的所有主机集合。当一台主机在广播域内发出一个广播帧时,同一广播域内的所有其他主机都可以收到该广播帧。在没有路由器或 VLAN 分割…...
Kafka 单机部署启动教程(适用于 Spark + Hadoop 环境)
🧭 Kafka 单机部署启动教程(适用于 Spark Hadoop 环境) 📦 一、Kafka 版本选择 推荐使用 Kafka 2.13-2.8.1(Scala 2.13,稳定适配 Spark 3.1.2 和 Hadoop 3.1.1) 下载地址(Apache 官…...

maven微服务${revision}依赖打包无法识别
1、场景描述 我现在又一个微服务项目,父pom的版本,使用<properties>定义好,如下所示: <name>ypsx-finance-center</name> <artifactId>ypsx-finance</artifactId> <packaging>pom</pack…...