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

原子类相关

原子引用

JUC 并发包提供了:

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference
AtomicReference 使用举例
public interface DecimalAccount {// 获取余额BigDecimal getBalance();// 取款void withdraw(BigDecimal amount);/*** 方法内会启动 1000 个线程,每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(DecimalAccount account) {List<Thread> ts = new ArrayList<>();for (int i = 0; i < 1000; i++) {ts.add(new Thread(() -> {account.withdraw(BigDecimal.TEN);}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(account.getBalance());}
}

如何提供 DecimalAccount 实现,实现安全的取款操作

不安全实现
class DecimalAccountUnsafe implements DecimalAccount {BigDecimal balance;public DecimalAccountUnsafe(BigDecimal balance) {this.balance = balance;}@Overridepublic BigDecimal getBalance() {return balance;}@Overridepublic void withdraw(BigDecimal amount) {BigDecimal balance = this.getBalance();this.balance = balance.subtract(amount);}
}
安全实现-使用synchronized
class DecimalAccountSafeLock implements DecimalAccount {private final Object lock = new Object();BigDecimal balance;public DecimalAccountSafeLock(BigDecimal balance) {this.balance = balance;}@Overridepublic BigDecimal getBalance() {return balance;}@Overridepublic void withdraw(BigDecimal amount) {synchronized (lock) {BigDecimal balance = this.getBalance();this.balance = balance.subtract(amount);}}
}
安全实现-使用CAS
class DecimalAccountSafeCas implements DecimalAccount {AtomicReference<BigDecimal> ref;public DecimalAccountSafeCas(BigDecimal balance) {ref = new AtomicReference<>(balance);}@Overridepublic BigDecimal getBalance() {return ref.get();}@Overridepublic void withdraw(BigDecimal amount) {while (true) {BigDecimal prev = ref.get();BigDecimal next = prev.subtract(amount);if (ref.compareAndSet(prev, next)) {break;}}}
}

测试代码

DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000")));
DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000")));
DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000")));

运行结果

4310 cost: 425 ms 
0 cost: 285 ms 
0 cost: 274 ms
AtomicStampedReference 使用举例

使用 AtomicReference 时线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到从 A 改为 B 又改回 A 的情况,如果主线程 希望: 只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号,使用 AtomicStampedReference 可以实现

static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);public static void main(String[] args) throws InterruptedException {log.debug("main start...");// 获取值 AString prev = ref.getReference();// 获取版本号int stamp = ref.getStamp();log.debug("版本 {}", stamp);// 如果中间有其它线程干扰,发生了 ABA 现象other();sleep(1);// 尝试改为 Clog.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}private static void other() {new Thread(() -> {log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp() + 1));log.debug("更新版本为 {}", ref.getStamp());}, "t1").start();sleep(0.5);new Thread(() -> {log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp() + 1));log.debug("更新版本为 {}", ref.getStamp());}, "t2").start();
}

输出如下,最后因为版本号不一致没有修改成功

15:21:34.891 c.Test36 [main] - main start... 
15:21:34.894 c.Test36 [main] - 版本 0 
15:21:34.956 c.Test36 [t1] - change A->B true 
15:21:34.956 c.Test36 [t1] - 更新版本为 1 
15:21:35.457 c.Test36 [t2] - change B->A true 
15:21:35.457 c.Test36 [t2] - 更新版本为 2 
15:21:36.457 c.Test36 [main] - change A->C false
AtomicMarkableReference 使用举例

AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了 AtomicMarkableReference。

class GarbageBag {String desc;public GarbageBag(String desc) {this.desc = desc;}public void setDesc(String desc) {this.desc = desc;}@Overridepublic String toString() {return super.toString() + " " + desc;}
}
@Slf4j
public class TestABAAtomicMarkableReference {public static void main(String[] args) throws InterruptedException {GarbageBag bag = new GarbageBag("装满了垃圾");// 参数2 mark 可以看作一个标记,表示垃圾袋满了AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);log.debug("主线程 start...");GarbageBag prev = ref.getReference();log.debug(prev.toString());new Thread(() -> {log.debug("打扫卫生的线程 start...");bag.setDesc("空垃圾袋");while (!ref.compareAndSet(bag, bag, true, false)) {}log.debug(bag.toString());}).start();Thread.sleep(1000);log.debug("主线程想换一只新垃圾袋?");boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);log.debug("换了么?" + success);log.debug(ref.getReference().toString());}
}

输出

15:30:09.264 [main] 主线程 start... 
15:30:09.270 [main] cn.itcast.GarbageBag@5f0fd5a0 装满了垃圾
15:30:09.293 [Thread-1] 打扫卫生的线程 start... 
15:30:09.294 [Thread-1] cn.itcast.GarbageBag@5f0fd5a0 空垃圾袋
15:30:10.294 [main] 主线程想换一只新垃圾袋?
15:30:10.294 [main] 换了么?false 
15:30:10.294 [main] cn.itcast.GarbageBag@5f0fd5a0 空垃圾袋

原子数组

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray
AtomicIntegerArray 使用举例

有如下方法

/**参数1,提供数组、可以是线程不安全数组或线程安全数组参数2,获取数组长度的方法参数3,自增方法,回传 array, index参数4,打印数组的方法
*/
// supplier 提供者 无中生有 ()->结果
// function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
// consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)-> void
private static <T> void demo(Supplier<T> arraySupplier,Function<T, Integer> lengthFun,BiConsumer<T, Integer> putConsumer,Consumer<T> printConsumer ) {List<Thread> ts = new ArrayList<>();T array = arraySupplier.get();int length = lengthFun.apply(array);for (int i = 0; i < length; i++) {// 每个线程对数组作 10000 次操作ts.add(new Thread(() -> {for (int j = 0; j < 10000; j++) {putConsumer.accept(array, j%length);}}));}ts.forEach(t -> t.start()); // 启动所有线程ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}); // 等所有线程结束printConsumer.accept(array);
}
不安全的数组
demo(()->new int[10],(array)->array.length,(array, index) -> array[index]++,array-> System.out.println(Arrays.toString(array))
);

结果

[9870, 9862, 9774, 9697, 9683, 9678, 9679, 9668, 9680, 9698]
安全的数组
demo(()-> new AtomicIntegerArray(10),(array) -> array.length(),(array, index) -> array.getAndIncrement(index),array -> System.out.println(array)
);

结果

[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

字段更新器

  • AtomicReferenceFieldUpdater // 域 字段
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater

利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现 IllegalArgumentException 异常

AtomicReferenceFieldUpdater 使用举例
public class Test {@Testpublic void test() {A a = new A();a.setNum(0);AtomicReferenceFieldUpdater<A, Integer> num = AtomicReferenceFieldUpdater.newUpdater(A.class, Integer.class, "num");List<Thread> ts = new ArrayList<>();for (int i = 0; i < 20; i++) {// 每个线程对数组作 10000 次操作ts.add(new Thread(() -> {for (int j = 0; j < 10000; j++) {while (true) {Integer prev = num.get(a);Integer next = prev + 1;if (num.compareAndSet(a, prev, next)) {break;}}}}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(num.get(a));}
}@Data
class A{volatile Integer num;
}

结果

200000

执行对比:

public class Test {@Testpublic void test() {A a = new A();a.setNum(new AtomicInteger(0));List<Thread> ts = new ArrayList<>();for (int i = 0; i < 20; i++) {// 每个线程对数组作 10000 次操作ts.add(new Thread(() -> {for (int j = 0; j < 1000; j++) {a.getNum().addAndGet(1);}}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(a.getNum().get());}
}@Data
class A {AtomicInteger num;
}
public class Test {@Testpublic void test() {A a = new A();a.setNum(new AtomicReference<>(0));List<Thread> ts = new ArrayList<>();for (int i = 0; i < 20; i++) {// 每个线程对数组作 10000 次操作ts.add(new Thread(() -> {for (int j = 0; j < 10000; j++) {while (true) {Integer prev = a.getNum().get();Integer next = prev + 1;if (a.getNum().compareAndSet(prev, next)) {break;}}}}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(a.getNum().get());}
}@Data
class A {AtomicReference<Integer> num;
}

AI 解释:

在第三个示例中,使用的是AtomicReference<Integer>,并且在每次递增时,需要显式地进行一个循环,在这个循环中不断尝试使用compareAndSet方法来安全地更新共享变量。在高并发的情况下,多个线程可能会竞争更新同一个变量,这可能导致多次失败的compareAndSet尝试,因而增加了循环的迭代次数。在第二个示例中,使用的是AtomicInteger,这是一个针对整数值的原子类。在这个示例中,使用了addAndGet方法,它是一个原子操作,无需显式循环来尝试更新。addAndGet方法会自动处理并发更新的问题,使得代码更加简洁,并且在性能上可能更优,因为它减少了失败更新尝试的可能性。在效率和性能方面,第二种方法(使用AtomicInteger和addAndGet)通常比第一种方法(使用AtomicReference<Integer>和显式循环)更优,原因包括:更少的重试次数:AtomicInteger的addAndGet方法通常会有更少的重试次数,因为它是专为数值运算设计的原子操作。
原子操作的优化:AtomicInteger的方法可能会更好地利用底层硬件的原子性支持,如使用CPU的原子指令集(比如compareAndSwap),从而提高操作的效率。
更简洁的代码:AtomicInteger提供了直接的方法来进行原子性数值操作,使得代码更简洁、更易于理解和维护。但是,实际性能的差异还是需要通过具体的性能测试来确定,因为它可能受到多种因素的影响,包括JVM实现、底层硬件、操作系统以及其他并发任务的特性。
--------------------------------------------------------------------------------------------
这第一段代码使用了AtomicReferenceFieldUpdater来以一种线程安全的方式更新A类的num字段。与前面的例子相比,这里有几个关键点需要注意:AtomicReferenceFieldUpdater的使用:AtomicReferenceFieldUpdater是一个原子更新器,它允许你以线程安全的方式更新指定类的指定volatile字段。这是一个高级工具,通常用于在不直接使用原子类(例如AtomicInteger)时优化内存使用和性能,因为它可以在不为每个字段创建额外对象的情况下实现字段的原子操作。字段定义:num字段定义为volatile类型的Integer,这意味着其修改对其他线程立即可见,但请注意volatile并不能保证复合(多步)操作的原子性。性能:尽管使用了AtomicReferenceFieldUpdater,但每次递增操作仍然需要显式的循环来尝试更新,类似于第三个示例中的AtomicReference<Integer>。这意味着高并发下可能会有多次失败的compareAndSet尝试,这可能导致性能上的开销。在性能和效率方面,尽管AtomicReferenceFieldUpdater提供了一种更轻量级的原子操作方式,这段代码的性能可能与第三个示例(使用AtomicReference<Integer>)相似,因为它也依赖于显式的重试循环来成功更新值。虽然AtomicReferenceFieldUpdater可能会比为每个变量创建一个AtomicReference对象更节省内存,但由于有失败重试的可能性,其性能可能不如第二个示例(使用AtomicInteger和addAndGet方法)。需要注意的是,AtomicReferenceFieldUpdater要求字段必须是volatile类型的,而且对于基本数据类型,比如int,应该使用相应的AtomicIntegerFieldUpdater。在此代码示例中,可能需要将Integer字段替换为int类型,并使用AtomicIntegerFieldUpdater,因为AtomicReferenceFieldUpdater通常是用于引用类型的字段。如果保持为Integer类型,可能会产生额外的装箱和拆箱操作,这将进一步影响性能。

原子累加器

LongAdder 使用举例
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {T adder = adderSupplier.get();List<Thread> ts = new ArrayList<>();// 4 个线程,每人累加 50 万for (int i = 0; i < 40; i++) {ts.add(new Thread(() -> {for (int j = 0; j < 500000; j++) {action.accept(adder);}}));}ts.forEach(t -> t.start());ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.nanoTime();System.out.println(adder + " cost:" + (end - start)/1000_000);
}

比较 AtomicLong 与 LongAdder

for (int i = 0; i < 5; i++) {demo(() -> new LongAdder(), adder -> adder.increment());
}for (int i = 0; i < 5; i++) {demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
}

测试结果:LongAdder 的累加性能更优,因为 LongAdder 在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加 Cell[1]… 总Cell个数不超过CPU核心数,最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能

Unsafe类

Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得,前面的原子整数、原子引用这些类的底层都是调用 Unsafe,Unsafe 底层会调用 CPU 的 lock 打头的指令之类

获得 Unsafe 对象的代码:

public class UnsafeAccessor {static Unsafe unsafe;static {try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe 是private的");// 因为 theUnsafe 是 private 的theUnsafe.setAccessible(true);// 因为 theUnsafe 是静态的,从属于类,不属于对象,所以传null即可获得字段值unsafe = (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}static Unsafe getUnsafe() {return unsafe;}
}
使用 Unsafe 对象实现 CAS 操作
@Data
class Student {volatile int id;volatile String name;
}
Unsafe unsafe = UnsafeAccessor.getUnsafe();Field id = Student.class.getDeclaredField("id");
Field name = Student.class.getDeclaredField("name");// 获得成员变量的偏移量
long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);
long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);Student student = new Student();
// 使用 cas 方法替换成员变量的值
UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 true
UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 true
System.out.println(student);

输出

Student(id=20, name=张三)

如何自己实现线程安全的整数自减操作?
class MyAtomicInteger {private volatile int num;static final Unsafe unsafe;static final long DATA_OFFSET;static {unsafe = UnsafeAccessor.getUnsafe();try {// num 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("num"));} catch (NoSuchFieldException e) {throw new Error(e);}}public MyAtomicInteger(int num) {this.num = num;}public void decrease(int amount) {while(true) {int prev = this.num;int next = prev - amount;// cas 尝试修改 num 为 旧值 + amount,如果期间旧值被别的线程改了,返回 falseif (unsafe.compareAndSwapInt(this, DATA_OFFSET, prev, next)) {return;}}}public int getNum() {return num;}
}

相关文章:

原子类相关

原子引用 JUC 并发包提供了&#xff1a; AtomicReferenceAtomicMarkableReferenceAtomicStampedReference AtomicReference 使用举例 public interface DecimalAccount {// 获取余额BigDecimal getBalance();// 取款void withdraw(BigDecimal amount);/*** 方法内会启动 10…...

RabbitMQ 客户端 连接、发送、接收处理消息

RabbitMQ 客户端 连接、发送、接收处理消息 一. RabbitMQ 的机制跟 Tcp、Udp、Http 这种还不太一样 RabbitMQ 服务&#xff0c;不是像其他服务器一样&#xff0c;负责逻辑处理&#xff0c;然后转发给客户端 而是所有客户端想要向 RabbitMQ服务发送消息&#xff0c; 第一步&a…...

Java Web 3 Axios Vue组件库

一 Ajax 1 同步 异步 2 原生Ajax 比较繁琐 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Documen…...

双目相机的标定,视差图,深度图,点云生成思路与实现。

该文档记录从双目相机标定到点云生成的所有过程&#xff0c;同时会附上代码。 代码直接能跑。https://github.com/stu-yzZ/stereoCamera 目录 大致思路如下&#xff1a; 一、相机标定 1、相机参数介绍 2、单目相机标定 3、双目相机标定 二、图片畸变矫正 三、极线矫正…...

【H2O2|全栈】MySQL的基本操作(三)

目录 前言 开篇语 准备工作 案例准备 多表查询 笛卡尔积 等值连接 外连接 内连接 自连接 子查询 存在和所有 含于 分页查询 建表语句 结束语 前言 开篇语 本篇继续讲解MySQL的一些基础的操作——数据字段的查询中的多表查询和分页查询&#xff0c;与单表查询…...

2、C++命名空间

命名空间 命名空间是一种用来避免命名冲突的机制; 原理是将一个全局的作用域分成一个个命名空间&#xff0c;每个命名空间是个单独的作用域,从而有效避免命名冲突。 注意&#xff1a;命名空间定义在全局 命名空间定义格式 使用&#xff1a; …...

Elemenu-UI时间日期单个组件,限制当前日期之后的时间

element的时间日期组件&#xff0c; type"datetime" &#xff0c;当你设置了:picker-options"pickerOptions"之后 pickerOptions: { disabledDate(time) { return time.getTime() > Date.now(); }, }, 会发现&#xff0c;他只会限制日期&#xff0c;但不…...

flutter修改状态栏学习

在flutter中如何动态更改状态栏的颜色和风格。 前置知识点学习 AnnotatedRegion AnnotatedRegion 是 Flutter 中的一个小部件&#xff0c;用于在特定区域中提供元数据&#xff08;metadata&#xff09;以影响某些系统级的行为或外观。它通常用于改变系统 UI 的外观&#xff…...

解决Unity编辑器Inspector视图中文注释乱码

1.问题介绍 新创建一个脚本&#xff0c;用VS打开编辑&#xff0c;增加一行中文注释保存&#xff0c;在Unity中找到该脚本并选中&#xff0c;Inspector视图中预览的显示内容&#xff0c;该中文注释显示为乱码&#xff0c;如下图所示&#xff1a; 2.图示解决步骤 按上述步骤操作…...

关于csgo的游戏作弊与封禁

关于csgo的游戏作弊与封禁 一.关于作弊 什么叫作弊&#xff1f; 1.换肤&#xff0c;换库存 2.各种参&#xff08;回溯&#xff0c;自瞄&#xff0c;透视&#xff0c;急停&#xff0c;连跳&#xff0c;假身&#xff0c;子弹跟踪等&#xff09; 3.某一部分更改游戏内存&…...

严格单元测试造就安全软件

在信息技术迅速发展的今天&#xff0c;软件在各个行业中扮演着至关重要的角色&#xff0c;尤其是在汽车行业&#xff0c;其中软件的可靠性和安全性直接影响到人们的生命安全。软件缺陷所带来的潜在风险不容小觑&#xff0c;尤其在涉及到自动驾驶和车辆控制等关键系统时&#xf…...

ubuntu 根分区逻辑卷扩容

1、虚拟机关机通过管理界面给磁盘扩容。 rootcurtis:/home/curtis/git_code# pvdisplay--- Physical volume ---PV Name /dev/vda3VG Name ubuntu-vgPV Size <239.00 GiB / not usable 0Allocatable yes (but full)PE…...

如何查看电脑生产日期

查看电脑的生产日期通常可以通过以下方法实现&#xff0c;具体方式取决于操作系统和电脑类型&#xff1a; 方法 1&#xff1a;检查电脑 BIOS 生产日期通常记录在 BIOS 中。可以通过以下步骤查看&#xff1a; 重启电脑并进入 BIOS&#xff1a; 启动时按下特定的键&#xff08;…...

MAC M1 mysql 8.0 如何修改root用户密码

关闭mysql服务 使用brew方式安装&#xff0c;可以通过一下命令关闭 brew services stop mysql使用安装包安装的方式 可以选择&#x1f34e;->系统偏好设置->最下方单机MySQL图标->stop mysql server 启动 MySQL 到安全模式 sudo mysqld_safe --skip-grant-tables …...

漫画之家系统:Spring Boot框架下的漫画版权保护

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&a…...

在 MacOS 上为 LM Studio 更换镜像源

在 MacOS 之中使用 LM Studio 部署本地 LLM时&#xff0c;用户可能会遇到无法下载模型的问题。 一般的解决方法是在 huggingface.co 或者国内的镜像站 hf-mirror.com 的项目介绍卡页面下载模型后拖入 LM Studio 的模型文件夹。这样无法利用 LM Studio 本身的搜索功能。 本文将…...

Nginx配置https(Ubuntu、Debian、Linux、麒麟)

Ubuntu操作系统&#xff0c;Debian系统底层是Ubuntu&#xff0c;差异不大 ubuntu 安装nginx 1.安装依赖 sudo apt-get update sudo apt-get install gcc sudo apt-get install libpcre3 libpcre3-dev sudo apt-get install zlib1g zlib1g-dev sudo apt-get install openssl lib…...

「Mac畅玩鸿蒙与硬件40」UI互动应用篇17 - 照片墙布局

本篇将带你实现一个简单的照片墙布局应用&#xff0c;通过展示多张图片组成照片墙效果&#xff0c;用户可以点击图片查看其状态变化。 关键词 UI互动应用照片墙布局Grid 布局动态图片加载用户交互 一、功能说明 照片墙布局应用的特点&#xff1a; 动态加载多张图片组成网格布…...

VMware Workstation 安装Ubuntu 系统(图文步骤)

之前一直在讲Ubuntu Linux的用户和组 链接&#xff1a; Linux专栏 今天来讲讲Ubuntu 系统基础的安装步骤&#xff01;&#xff01;&#xff01; 废话少说&#xff0c;马上开始&#xff01; 文章目录 前言准备安装环境先下载Ubuntu 镜像 详细安装步骤如下新建虚拟机默认使用 15.…...

mybatis用pagehelper 然后用CountJSqlParser45,发现自己手写的mapper查询效率很慢

如题 效率慢疑惑 效率慢 分页查询,发现效率很慢,然后发现是比较复杂的sql,CountJSqlParser45它不会帮忙优化掉,就是select多少字段它count的时候也还是这么多字段 框架里的用法是这样的 所以去看了CountJSqlParser45里面的代码,发现如果有group之类的,它就不帮忙把count优化…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)

LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 题目描述解题思路Java代码 题目描述 题目链接&#xff1a;LeetCode 3309. 连接二进制表示可形成的最大数值&#xff08;中等&#xff09; 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...

PHP 8.5 即将发布:管道操作符、强力调试

前不久&#xff0c;PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5&#xff01;作为 PHP 语言的又一次重要迭代&#xff0c;PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是&#xff0c;借助强大的本地开发环境 ServBay&am…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...