当前位置: 首页 > news >正文

聊聊并发编程——多线程之synchronized

目录

一.多线程下数据不一致问题

二.锁和synchronized

2.1 并发编程三大特性

2.2引入锁概念

三.synchronized的锁实现原理

3.1 monitorenter和monitorexit

3.2synchronized 锁的升级

3.2.1偏向锁的获取和撤销

3.2.2轻量级锁的加锁和解锁

自适应自旋锁

轻量级锁的解锁

3.2.3重量级锁—线程阻塞

3.2.4锁的优缺点对比

3.3CAS实现原子性


一.多线程下数据不一致问题

我们知道,在并发编程中,多个线程同时访问共享资源时可能导致数据不一致、死锁、性能问题等严重后果。

像下面这个例子,i的值最后是多少呢?

简单的demo实验下:

public class Demo {private static int count = 0;public static void inc() {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}count++;}
​public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 100; i++) {new Thread(() -> Demo.inc()).start();}Thread.sleep(3000);System.out.println("运行结果" + count);}
}

多次的运行结果是不相同的,并不是预期的100:

针对数据不一致的问题,我们先了解下java内存模型(Java Memory Model, JMM),它是一种抽象的模型,被定义出来屏蔽各种硬件和操作系统的内存访问差异。JMM定义了线程与主内存之间的抽象关系:线程之间的共享变量存储在主内存中,而每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

结合JMM不难理解,线程t1和t2通过操作本地内存中的副本进行刷新存储在主内存上的共享变量对象,但它们都没有在进行修改操作后立即告知其它线程。针对多线程并发访问共享资源导致数据不一致的问题,java给出了类似Synchronized和volatile的解决方案。

二.锁和synchronized

2.1 并发编程三大特性

并发编程中有三大核心特性,分别是原子性(Atomicity)、可见性(Visibility)、有序性(Ordering),通常被简称为 AVO 特性。这些特性对于多线程编程非常重要,因为它们确保了多线程环境下的正确性和可靠性。

  1. 原子性(Atomicity):

    • 原子性指的是一个操作是不可分割的单元,要么全部执行成功,要么全部不执行,不会被中断。原子操作可以看作是线程安全的,多个线程可以同时执行这个操作,而不会破坏操作的完整性。

    • 原子性保证了操作的完整性,避免了竞态条件,通常通过锁、原子类、事务等机制来实现。

  2. 可见性(Visibility):

    • 可见性指的是一个线程对共享变量的修改能够被其他线程立即感知到,即修改后的值在主内存中对其他线程是可见的。在多线程环境下,如果不采取适当的同步措施,共享变量的修改可能对其他线程不可见,导致意外的行为和错误。

    • 可以使用volatile关键字来确保变量的可见性,或者使用锁机制(如synchronized关键字)来同步访问共享变量。

  3. 有序性(Ordering):

    • 有序性指的是程序的执行顺序与代码的编写顺序一致,即代码按照预期的顺序执行,不会出现乱序或重排序的情况。

    • 在现代计算机架构中,为了提高性能,编译器和处理器可能会对指令进行重排序,但这种重排序在单线程环境下不会引发问题。然而,在多线程环境下,如果不正确地控制重排序,可能会导致不一致的结果。

    • 可以使用同步机制来确保有序性,例如在进入和退出锁的范围内,编译器和处理器会执行必要的指令重排序来维护有序性。

2.2引入锁概念

在可见性的理解上,java引入了锁的概念,用于防止多个线程同时访问或修改共享资源,以确保线程安全性。

而加锁的方式是使用synchronized关键字。

public class Demo {private static int count = 0;// 使用Synchronized加锁public static synchronized void inc() {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}count++;}
​public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 100; i++) {new Thread(() -> Demo.inc()).start();}Thread.sleep(3000);System.out.println("运行结果" + count);}
}

synchronized 关键字在 Java 中主要用于实现互斥锁,它主要保证了并发编程中的原子性和可见性这两个特性。

  1. 原子性(Atomicity):

    synchronized 保证了被 synchronized 修饰的代码块或方法在同一时间只能被一个线程执行,这确保了其中的操作是原子的,即不会被中断或同时被其他线程访问。这有助于防止竞态条件和确保操作的完整性。

  2. 可见性(Visibility):

    当一个线程进入 synchronized 块或方法时,它会获取锁,并在释放锁时将修改的数据从线程的工作内存同步到主内存,这就确保了其他线程可以立即看到最新的数据。这满足了可见性要求,确保了共享数据的变更对其他线程是可见的。

synchronized 关键字在 Java 中有三种主要的用法,用于实现不同类型的同步:

  1. 实例方法同步:

    使用 synchronized 关键字修饰普通的实例方法,这会将锁定范围限制在该方法所属对象实例上,即同一对象的不同线程在同时访问这个方法时会互斥执行。这种方式适用于对实例变量的同步访问。

    public synchronized void instanceMethod() {// 这里的代码是线程安全的
    }
  2. 静态方法同步:

    使用 synchronized 关键字修饰静态方法,这会将锁定范围限制在类的 Class 对象上,即同一类的不同对象的不同线程在同时访问这个静态方法时会互斥执行。这种方式适用于对静态变量的同步访问。

    public static synchronized void staticMethod() {// 这里的代码是线程安全的
    }
  3. 同步代码块:

    使用 synchronized 关键字修饰代码块,可以精确地指定需要同步的对象。这允许你在方法内的特定部分实现同步,而不是整个方法。你需要指定一个对象作为锁,当多个线程尝试进入同步代码块时,它们必须获取相同的锁才能执行。

    public void someMethod() {// 一些非同步代码synchronized (lockObject) {// 这里的代码是线程安全的,锁定的是 lockObject 对象}// 更多非同步代码
    }

    设计模式中

    [单例模式的双重检查锁定]  https://blog.csdn.net/Elaine2391/article/details/132675080 

    其实就是一个简单的Synchronized和volatile的应用。

三.synchronized的锁实现原理

3.1 monitorenter和monitorexit

我们使用synchronized的时候没有手动的lock和unlock,那么synchronized是怎么加锁的呢?其实这里JVM帮我们处理了。反编译⼀段synchronized修饰代码块代码来看看:

找到上面我们的Demo类文件路径,javac Demo.java后生成class文件, javap -v Demo.class ,可以看到相应的字节码指令。synchronized修饰静态方法时,JVM采用monitorenter和monitorexit两个指令来实现同步,monitorenter 指令指向同步代码块的开始位置, monitorexit 指令则指向同步代码块的结束位置。

而且synchronized修饰同步方法时,JVM采用ACC_SYNCHRONIZED标记符实现同步,这个标识指明了该方法是一个同步方法。

monitorenter、monitorexit或者ACC_SYNCHRONIZED都是基于Monitor实现的。Monitor是什么呢?

所谓的Monitor其实是⼀种同步⼯具,也可以说是⼀种同步机制。在Java虚拟机(HotSpot)中,Monitor是由 ObjectMonitor实现的,可以叫做内部锁,或者Monitor锁。线程在获取锁的时候,实际上就是获得一个监视器对象(monitor),monitor可以认为是一个同步对象,所有的Java对象是天生携带monitor。多个线程访问同步代码块时,相当于去争抢对象监视器修改对象中的锁标识,ObjectMonitor这个对象和线程争抢锁的逻辑有密切的关系。

3.2synchronized 锁的升级

在JVM的自动内存管理中分析markword时,提到了偏向锁、轻量级锁、重量级锁和无锁状态。分析前我们先来思考一个问题:使用锁能够实现数据的安全性,但是会带来性能的下降。不使用锁能提高性能又不能保证线程安全性。怎么办呢?

其实hotspot的作者经过调查发现,假设加锁的同步块分为下面三种情况,而大部分情况下,是处于第一种。

  1. 只有线程A进入临界区。(偏向锁)

  2. 线程A和线程B交替进入临界区。(轻量级锁)

  3. 线程A、线程B、线程C同时进入临界区。(重量级锁)

而这也是JDK1.6之后synchronized做出的优化,为了减少获得锁和释放锁带来的性能开销,引入了偏向锁、轻量级锁的概念。所以synchronized中,锁存在四种状态,分别是无锁、偏向锁、轻量级锁、重量级锁;锁的状态根据竞争激烈的程度从低到高不断升级。

针对共享资源对象加锁的操作,其实真正操作的是对象头中的markword。

img

3.2.1偏向锁的获取和撤销
  • 首先获取锁对象的Markword,判断是否处于可偏向状态。(偏向锁位biased_lock=0且ThreadId为空,表示未偏向任何线程)

  • 如果是可偏向状态,则通过CAS操作,把当前线程的ID写入到Markword

    a)如果CAS成功,那么就表示线程获取了锁对象的偏向锁,锁状态从无锁升级为偏向锁。

    b)如果CAS失败,说明其他线程已获得了偏向锁。这种情况说明当前存在锁竞争,需要暂停已获得偏向锁的线程,撤销偏向锁,升级为轻量级锁(这个操作在全局安全点执行,就是没有线程在执行字节码的时候)

  • 如果是已偏向状态,需要检查Markword中存储的ThreadId是否等于当前线程的ThreadId

    a)如果相等,不需要再次获得锁,可直接执行同步代码块。这就避免了锁的竞争和线程上下文切换。

    b)如果不相等,说明当前锁偏向于其他线程,需要撤销锁并升级为轻量级锁

对于原持有偏向锁的线程进行撤销时,原获得偏向的线程有两种情况:

a)原获得偏向锁的线程已经退出了临界区,就是执行完了代码块,这时候就会把对象头设置为无锁状态,并且争抢锁的进程可以基于CAS重新设置对象头偏向当前线程。

b)原获得偏向锁的线程还在执行同步块,这个时候就会将原获得偏向锁的线程的偏向锁升级为轻量级锁。

但是根据我们实际情况,绝大部分时候一定会存在2个以上的线程竞争,那么开启偏向锁反而提升了获取锁的资源消耗,可以通过jvm参数UserBiasedLocking来设置开启或关闭偏向锁。流程图分析如下:

3.2.2轻量级锁的加锁和解锁
  • 首先,jvm会判断是否是重量级锁,如果不是,会在当前线程栈帧中划出一块空间,作为该锁的锁记录,并且将锁对象Markword复制到该锁记录中。

  • 复制成功后,jvm使用CAS操作将对象头Markword更新为指向锁记录的指针,并且将锁记录里的own指针指向对象头的Markword。

  • 更新对象头Markword成功的情况,当前线程持有对象锁,并且对象Markword锁标志设为‘00’,即表示此对象处于轻量级锁状态。

  • 更新对象头Markword失败的情况,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录,如果是就表示锁重入。如果不是就表示锁对象被其他线程抢占,进行自旋等待(默认10次),等待次数达到阈值仍未获取到锁,膨胀为重量级锁。

自适应自旋锁

自旋锁就是锁在原地循环执行一个啥都没有的for循环操作,是会消耗cpu的。JDK1.6后引入了自适应自旋锁,自适应意味着着自旋的次数不是固定不变的,而是根据前一次在同一个锁上自旋的时间以及锁的拥有者的状态来决定。

如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自

旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。

如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

轻量级锁的解锁

就是获得锁的逆向逻辑,通过CAS操作把栈帧中的LockRecord替换回锁对象的Markword中,如果成功表示没有竞争。如果失败,表示当前锁存在竞争,那么轻量级锁就会膨胀为重量级锁。流程图如下:

3.2.3重量级锁—线程阻塞

轻量级锁膨胀到重量级锁后,线程只能被挂起阻塞来等待被唤醒了。阻塞是重量级锁的标志。重量级锁加锁的基本流程:

举例说明下:

定义线程A传入锁对象lock,包含一个简单的wait操作。

public class ThreadA extends Thread{private Object lock;public  ThreadA(Object lock){this.lock = lock;}@Overridepublic void run() {synchronized (lock) {System.out.println("ThreadA start");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("ThreadA end");}}
}

定义线程B传入锁对象,包含一个notify操作。

public class ThreadB extends Thread{private Object lock;public ThreadB(Object lock){this.lock = lock;}@Overridepublic void run() {synchronized (lock) {System.out.println("ThreadB start");lock.notify();System.out.println("ThreadB end");}}
}

创建A线程启动,B线程启动,执行结果:

public class WaitNotify {public static void main(String[] args) {Object lock = new Object();ThreadA threadA = new ThreadA(lock);threadA.start();
​ThreadB threadB = new ThreadB(lock);threadB.start();}
}

3.2.4锁的优缺点对比

3.3CAS实现原子性
  • CAS叫做CompareAndSwap,⽐较并交换,主要是通过处理器的指令来保证操作的原⼦性的。

  • CAS 指令包含 3 个参数:共享变量的内存地址 A、预期的值 B 和共享变量的新值 C。

  • 只有当内存中地址 A 处的值等于 B 时,才能将内存中地址 A 处的值更新为新值 C。作为⼀条 CPU 指令,CAS 指令 本身是能够保证原⼦性的

CAS操作三大问题

  • ABA问题,在比较期间,发生了A-B-A,CAS感知不到过程,解决方案:加版本号。1A-2B-3A.

  • 循环时间长开销大

  • 只能保证一个共享变量的原子操作

相关文章:

聊聊并发编程——多线程之synchronized

目录 一.多线程下数据不一致问题 二.锁和synchronized 2.1 并发编程三大特性 2.2引入锁概念 三.synchronized的锁实现原理 3.1 monitorenter和monitorexit 3.2synchronized 锁的升级 3.2.1偏向锁的获取和撤销 3.2.2轻量级锁的加锁和解锁 自适应自旋锁 轻量级锁的解锁…...

CompletableFuture-通用异步编程

演示Completable接口完全可以代替Future接口&#xff1a; CompletableFuture减少阻塞和轮询&#xff0c;可以传入回调对象&#xff0c;当异步任务完成或者发生异常时&#xff0c;自动 调用回调对象的回调方法。 package com.nanjing.gulimall.zhouyimo.test;import java.util…...

Vue3 封装 element-plus 图标选择器

一、实现效果 二、实现步骤 2.1. 全局注册 icon 组件 // main.ts import App from ./App.vue; import { createApp } from vue; import * as ElementPlusIconsVue from element-plus/icons-vueconst app createApp(App);// 全局挂载和注册 element-plus 的所有 icon app.con…...

超详细C语言实现——通讯录

目录 一、介绍 二、源代码 test.c: Contact.c: Contact.h: 代码运行结果&#xff1a; 三、开始实现 1.基本框架&#xff1a; 2.添加联系人&#xff1a; 3.显示联系人信息&#xff1a; 4.删除联系人信息&#xff1a; 5.查看指定联系人信息&#xff1a; 6.修改联系人…...

zabbix监控添加监控项及其监控Mysql、nginx

本届主要介绍添加监控项和修改中文乱码&#xff0c;监控mysql&#xff0c;nginx服务 一、zabbix监控添加监控项 1、配置agent服务器 在配置文件中添加&#xff1a; UserParameterlsq_userd,free -m | grep Mem | awk { print $3 } 服务器内存使用量 UserParameterdu,…...

Docker 部署 MongoDB 服务

拉取最新版本的 MongoDB 镜像&#xff1a; $ sudo docker pull mongo:latest在本地预先创建好 db 和 configdb 目录, 用于映射 MongoDB 容器内的 /data/db 和 /data/configdb 目录。 使用以下命令来运行 MongoDB 容器: $ sudo docker run -itd --name mongo --privilegedtru…...

QUIC协议报文解析(三)

在前面的两篇文字里我们简单介绍了QUIC的发展历史&#xff0c;优点以及QUIC协议的连接原理。本篇文章将会以具体的QUIC报文为例&#xff0c;详细介绍QUIC报文的结构以及各个字段的含义。 早期QUIC版本众多&#xff0c;主要有谷歌家的gQUIC&#xff0c;以及IETF致力于将QUIC标准…...

pytorch迁移学习训练图像分类

pytorch迁移学习训练图像分类 一、环境配置二、迁移学习关键代码三、完整代码四、结果对比 代码和图片等资源均来源于哔哩哔哩up主&#xff1a;同济子豪兄 讲解视频&#xff1a;Pytorch迁移学习训练自己的图像分类模型 一、环境配置 1&#xff0c;安装所需的包 pip install …...

SQL 如何提取多级分类目录

前言 POI数据处理&#xff0c;原始数据为csv格式&#xff0c;整理入库至PostGreSQL&#xff0c;本例使用PostGreSQL13版本。 一、POI POI&#xff08;一般作为Point of Interest的缩写&#xff0c;也有Point of Information的说法&#xff09;&#xff0c;通常称作兴趣点&am…...

从中序遍历和后序遍历构建二叉树

题目描述 106. 从中序与后序遍历序列构造二叉树 中等 1.1K 相关企业 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1…...

《计算机视觉中的多视图几何》笔记(11)

11 Computation of the Fundamental Matrix F F F 本章讲述如何用数值方法在已知若干对应点的情况下求解基本矩阵 F F F。 文章目录 11 Computation of the Fundamental Matrix F F F11.1 Basic equations11.1.1 The singularity constraint11.1.2 The minimum case – sev…...

UE5 ChaosVehicles载具研究

一、基本组成 载具Actor类名称&#xff1a;WheeledVehiclePawn Actor最原始的结构 官方增加了两个摇臂相机&#xff0c;可以像驾驶游戏那样切换多机位、旋转观察 选择骨骼网格体、动画蓝图类、开启物理模拟 二、SportsCar_Pawn 角阻尼&#xff1a;物体旋转的阻力。数值越大…...

数据通信——应用层(域名系统)

引言 TCP到此就告一段落&#xff0c;这也意味着传输层结束了&#xff0c;紧随其后的就是TCP/IP五层架构的应用层。操作系统、编程语言、用户的可视化界面等等都要通过应用层来体现。应用层和我们息息相关&#xff0c;我们使用电子设备娱乐或办公时&#xff0c;接触到的就是应用…...

Visual Studio 更新:远程文件管理器

Visual Studio 中的远程文件管理器可以用来访问远程机器上的文件和文件夹&#xff0c;通过 Visual Studio 自带的连接管理器&#xff0c;可以实现不离开开发环境直接访问远程系统&#xff0c;这确实十分方便。 自从此功能发布以来&#xff0c;VS 开发团队努力工作&#xff0c;…...

ChatGPT追祖寻宗:GPT-3技术报告要点解读

论文地址&#xff1a;Language Models are Few-Shot Learners 往期相关文章&#xff1a; ChatGPT追祖寻宗&#xff1a;GPT-1论文要点解读_五点钟科技的博客-CSDN博客ChatGPT追祖寻宗&#xff1a;GPT-2论文要点解读_五点钟科技的博客-CSDN博客 本文的标题之所以取名技术报告而不…...

java easyexcel 导出多级表头

maven <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>${easyexcel.version}</version> </dependency> 导出行的对象 import com.alibaba.excel.annotation.ExcelIgnore; import …...

rar格式转换zip格式,如何做?

平时大家压缩文件时对压缩包格式可能没有什么要求&#xff0c;但是&#xff0c;可能因为工作需要&#xff0c;我们要将压缩包格式进行转换&#xff0c;那么我们如何将rar格式转换为其他格式呢&#xff1f;方法如下&#xff1a; 工具&#xff1a;WinRAR 打开WinRAR&#xff0c…...

Java中的构造方法

在Java中&#xff0c;构造方法是类的特殊方法&#xff0c;用于初始化对象的实例变量和执行其他必要的操作&#xff0c;以便使对象能够正确地工作。构造方法与类同名&#xff0c;没有返回类型&#xff0c;并且在创建对象时自动调用。 以下是构造方法的一些基本特性&#xff1a;…...

【Java】fastjson

Fastjson简介 Fastjson是阿里巴巴的团队开发的一款Java语言实现的JSON解析器和生成器&#xff0c;它具有简单易用、高性能、高可用性等优点&#xff0c;适用于Java开发中的数据解析和生成。Fastjson的主要特点包括&#xff1a; 简单易用&#xff1a;Fastjson提供了简单易用的…...

JMeter之脚本录制

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 前言&#xff1a; 对于一些JMeter初学者来说&#xff0c;录制脚本可能是最容易掌握的技能之一。…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例

目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码&#xff1a;冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...

Java后端检查空条件查询

通过抛出运行异常&#xff1a;throw new RuntimeException("请输入查询条件&#xff01;");BranchWarehouseServiceImpl.java // 查询试剂交易&#xff08;入库/出库&#xff09;记录Overridepublic List<BranchWarehouseTransactions> queryForReagent(Branch…...