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

JAVA SE 多线程(下)

文章目录

    • 📕1. 常见的锁策略
        • ✏️1.1 乐观锁VS悲观锁
        • ✏️1.2 轻量级锁VS重量级锁
        • ✏️1.3 自旋锁
        • ✏️1.4 公平锁VS非公平锁
        • ✏️1.5 可重入锁和不可重入锁
        • ✏️1.6 读写锁
    • 📕2. 死锁
        • ✏️2.1 哲学家就餐问题
        • ✏️2.2 形成死锁的必要条件
        • ✏️2.3 如何避免死锁
    • 📕3. JUC(java.util.concurrent) 的常见类
        • ✏️3.1 Callable 接口
        • ✏️3.2 ReentrantLock
        • ✏️3.3 原子类
        • ✏️3.4 信号量 Semaphore
        • ✏️3.5 CountDownLatch
    • 📕4. synchronized 原理
        • ✏️4.1 加锁工作过程
        • ✏️4.2 其他的优化操作
    • 📕5. CAS
        • ✏️5.1 CAS的ABA问题

📕1. 常见的锁策略

注意 : 以下所介绍的锁策略, 不仅仅局限于Java这一种语言 , 这些性质通常也是给锁的实现者参考的.当然我们普通人也是可以了解一下的 , 这或许对使用锁也有帮助.

✏️1.1 乐观锁VS悲观锁

悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

乐观锁: 假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

🌰举个栗子: 同学 A 和 同学 B 想请教老师一个问题.

同学 A 认为 “老师是比较忙的, 我来问问题, 老师不一定有空解答”. 因此同学 A 会先给老师发消息: “老师你忙嘛? 我下午两点能来找你问个问题嘛?” (相当于加锁操作) 得到肯定的答复之后, 才会真的来问问题.如果得到了否定的答复, 那就等一段时间, 下次再来和老师确定时间. 这个是悲观锁.

同学 B 认为 “老师是比较闲的, 我来问问题, 老师大概率是有空解答的”. 因此同学 B 直接就来找老师.(没加锁, 直接访问资源) 如果老师确实比较闲, 那么直接问题就解决了. 如果老师这会确实很忙, 那么同学 B也不会打扰老师, 就下次再来(虽然没加锁, 但是能识别出数据访问冲突). 这个是乐观锁.

Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略.

就好比同学 C 开始认为 “老师比较闲的”, 问问题都会直接去找老师.但是直接来找两次老师之后, 发现老师都挺忙的, 于是下次再来问问题, 就先发个消息问问老师忙不忙,再决定是否来问问题.

✏️1.2 轻量级锁VS重量级锁

轻量级锁: 当一个线程尝试获取某个对象的锁时,如果该对象没有被其他线程锁定,则当前线程会将对象头中的Mark Word设置为指向当前线程栈帧的一个指针,这个过程称为“偏向锁”。如果多个线程同时竞争同一个锁,那么JVM会升级锁的状态,从偏向锁升级到轻量级锁。此时,每个线程都会尝试使用CAS操作来获取锁,如果成功则获得锁并进入临界区;如果失败,则自旋等待一段时间后再次尝试。

特定:

  1. 减少了操作系统上下文切换的开销。
  2. 在线程间竞争不激烈的情况下表现良好。
  3. 如果竞争过于激烈,可能会导致频繁的自旋,浪费CPU资源。

重量级锁: 传统的Java锁机制,如synchronized关键字所实现的锁,通常被称为重量级锁。当一个线程获取了某个对象的锁后,其他试图获取同一对象锁的线程会被阻塞,直到第一个线程释放锁为止。被阻塞的线程将进入等待队列,由操作系统负责管理这些线程的调度。

特点:

  1. 线程阻塞和唤醒的代价较高。
  2. 更适用于线程竞争激烈的场景,因为它可以避免CPU空转浪费资源。
  3. 相比轻量级锁,重量级锁的实现更加简单直接。
✏️1.3 自旋锁

按之前的方式,线程在抢锁失败后进⼊阻塞状态,放弃 CPU,需要过很久才能再次被调度 . 但实际上, 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU. 这个时候就可以使用自旋锁来处理这样的问题 , 如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止.

理解⾃旋锁 vs 挂起等待锁
想象⼀下, 去追求⼀个⼥神. 当男⽣向⼥神表⽩后, ⼥神说: 你是个好⼈, 但是我有男朋友了~~
挂起等待锁: 陷⼊沉沦不能⾃拔.... 过了很久很久之后, 突然⼥神发来消息, '咱俩要不试试?' (注意, 这
个很⻓的时间间隔⾥, ⼥神可能已经换了好⼏个男朋友了).
⾃旋锁: 死⽪赖脸坚韧不拔. 仍然每天持续的和⼥神说早安晚安. ⼀旦⼥神和上⼀任分⼿, 那么就能⽴刻
抓住机会上位.

优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁.
缺点: 如果锁被其他线程持有的时间比较久, 那么就会持续的消耗 CPU 资源. (而挂起等待的时候是不消耗 CPU 的).

✏️1.4 公平锁VS非公平锁

假设三个线程 A, B, C. A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后 C也尝试获取锁, C 也获取失败, 也阻塞等待. 当线程 A 释放锁的时候, 会发生啥呢?

公平锁 : 遵守 “先来后到”. B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.

非公平锁 : 不遵守 “先来后到”. B 和 C 都有可能获取到锁

这就好⽐⼀群男⽣追同⼀个⼥神. 当⼥神和前任分⼿之后, 先来追⼥神的男⽣上位, 这就是公平锁; 
如果是⼥神不按先后顺序挑⼀个⾃⼰看的顺眼的, 就是⾮公平锁.

在这里插入图片描述
公平锁
在这里插入图片描述
非公平锁
在这里插入图片描述
💡💡💡注意 :

  1. 操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.
  2. 公平锁和非公平锁没有好坏之分, 关键还是看适用场景.
  3. synchronized 是非公平锁
✏️1.5 可重入锁和不可重入锁

可重入锁: 简单来说就是一个线程如果抢占到了互斥锁资源,在锁释放之前再去该竞争同一把锁的时候,不需要等待,只需要记录重入次数。在多线程并发编程里面,绝大部分锁都是可重入的,比如Synchronized、ReentrantLock等,但是也有不支持重入的锁,比如JDK8里面提供的读写锁StampedLock。锁的可重入性,主要解决的问题是避免线程死锁的问题。

✏️1.6 读写锁

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同⼀个锁,就会产生极大的性能损耗。所以读写锁因此而产生。

读写锁(readers-writer lock),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。

⼀个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.

两个线程都只是读⼀个数据, 此时并没有线程安全问题. 直接并发的读取即可.
两个线程都要写⼀个数据, 有线程安全问题.
⼀个线程读另外⼀个线程写, 也有线程安全问题

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁

ReentrantReadWriteLock.ReadLock 类表示⼀个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.
ReentrantReadWriteLock.WriteLock 类表示⼀个写锁. 这个对象也提供了 lock / unlock方法进行加锁解锁.

其中,
读加锁和读加锁之间, 不互斥.
写加锁和写加锁之间, 互斥.
读加锁和写加锁之间, 互斥

读写锁特别适合于 “频繁读, 不频繁写” 的场景中.

📕2. 死锁

什么是死锁?
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

🌰举个栗子理解死锁 :

当我和我对象(当然现在还没有😭)⼀起去饺子馆吃饺子时 , 吃饺子需要酱油和醋.

我拿起了酱油瓶, 我对象拿起了醋瓶.

我 : 你先把醋瓶给我, 我用完了就把酱油瓶给你.

我对象 : 你先把酱油瓶给我, 我用完了就把醋瓶给你.

如果我们俩彼此之间互不相让, 就构成了死锁.

酱油和醋相当于是两把锁, 我们两个人就是两个线程.

✏️2.1 哲学家就餐问题

有个桌子 , 围着一圈哲学家 , 桌子中间放着一盘意大利面 . 每个哲学家两两之间, 放着一根筷子.
在这里插入图片描述

每个哲学家只做两件事 : 思考人生或者吃面条. 思考人生的时候就会放下筷子. 吃⾯条就会拿起左右两边的筷子(先拿起左边, 再拿起右边).
在这里插入图片描述
如果哲学家发现筷子拿不起来了 , 就会阻塞等待
在这里插入图片描述
关键点 : 如果5位哲学家同时拿起左手边的筷子时 , 然后再尝试拿右手的筷子, 就会发现右手的筷子都被占用了. 由于哲学家们互不相让, 这个时候就形成了死锁
在这里插入图片描述
死锁是一种严重的 BUG!! 导致一个程序的线程 “卡死”, 无法正常工作!

✏️2.2 形成死锁的必要条件

死锁产生的四个必要条件:

  1. 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

  2. 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

  3. 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

  4. 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成⽴的时候,便形成死锁。当然,死锁的情况下如果打破上述任何⼀个条件,便可让死锁消失。

✏️2.3 如何避免死锁

破坏循环等待

最常用的一种死锁阻止技术就是锁排序. 假设有 N 个线程尝试获取 M 把锁, 就可以针对 M 把锁进行编号 (1, 2, 3…M).

N 个线程尝试获取锁的时候, 都按照固定的按编号由小到⼤顺序来获取锁. 这样就可以避免环路等待

可能产生循环等待死锁的代码

//产生环路等待不是100%发生的,这只是概率问题,哲学家就餐产生死锁也是概率问题
public class Test {private static Object locker1 = new Object();private static Object lockre2 = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (locker1) {synchronized (lockre2) {System.out.println("this is thread t1");}}});Thread t2 = new Thread(() -> {synchronized (lockre2) {synchronized (locker1) {System.out.println("this is thread t2");}}});t1.start();t2.start();}
}

不会产生循环等待的代码

public class Test {private static Object locker1 = new Object();private static Object lockre2 = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (locker1) {synchronized (lockre2) {System.out.println("this is thread t1");}}});Thread t2 = new Thread(() -> {synchronized (locker1) {synchronized (lockre2) {System.out.println("this is thread t2");}}});t1.start();t2.start();}
}

📕3. JUC(java.util.concurrent) 的常见类

✏️3.1 Callable 接口

Callable 是一个 interface . 相当于把线程封装了一个 “返回值”. 方便我们借助多线程的方式计算结果

🌰代码示例: 创建线程计算 1 + 2 + 3 + … + 1000, 使用 Callable 版本

  1. 创建一个匿名内部类, 实现 Callable 接口. Callable 带有泛型参数. 泛型参数表示返回值的类型.
  2. 重写 Callable 的 call 方法, 完成累加的过程. 直接通过返回值返回计算结果.
  3. 把 callable 实例使用 FutureTask 包装一下
  4. 创建线程, 线程的构造方法传入FutureTask . 此时新线程就会执行FutureTask 内部的 Callable 的call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中.
  5. 在主线程中调用futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int result = 0;for (int i = 0; i <= 1_000; i++) {result+=i;}return result;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();System.out.println(futureTask.get());}
}//500500

理解Callable

Callable 和 Runnable 相对, 都是描述一个 “任务”. Callable 描述的是带有返回值的任务, Runnable描述的是不带返回值的任务.
Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定. FutureTask 就可以负责这个等待结果出来的工作.

理解 FutureTask
想象去吃麻辣烫. 当餐点好后, 后厨就开始做了. 同时前台会给你一张 “小票” . 这个小票就是FutureTask. 后面我们可以随时凭这张小票去查看自己的这份麻辣烫做出来了没.

✏️3.2 ReentrantLock

可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.

ReentrantLock 的用法:

lock(): 加锁, 如果获取不到锁就死等.

trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.

unlock(): 解锁

ReentrantLock 和 synchronized 的区别:

  1. synchronized 是一个关键字, 是 JVM 内部实现的 , ReentrantLock 是标准库的一个类, 在 JVM 外实现的(基于 Java 实现).
  2. synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活, 但是也容易遗漏 unlock.
  3. synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃
  4. synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式.
  5. 更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.

如何选择使用哪个锁?

  1. 锁竞争不激烈的时候, 使用 synchronized, 效率更高, 自动释放更方便
  2. 锁竞争激烈的时候, 使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为, 而不是死等
  3. 如果需要使用公平锁, 使用 ReentrantLock
✏️3.3 原子类

原子类内部用的是 CAS 实现,所以性能要比加锁实现 i++ ⾼很多。原子类有以下几个:

  1. AtomicBoolean
  2. AtomicInteger
  3. AtomicIntegerArray
  4. AtomicLong
  5. AtomicReference
  6. AtomicStampedReference

虽然原子类有多 , 但是很抱歉 , 有很多原子类我也并没有使用过 , 所提具体在什么场景下使用什么原子类 , 也是我的知识盲区😭

以 AtomicInteger 举例,常见方法有:

addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i--;
incrementAndGet(); ++i;
getAndIncrement(); i++;
✏️3.4 信号量 Semaphore

信号量, 用来表示 “可用资源的个数”. 本质上就是一个计数器.

🌰举个栗子理解一下:

可以把信号量想象成是停车场的展示牌: 当前有车位 100 个. 表示有 100 个可用资源.

当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作(P是荷兰语单词首字母))

当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作(V是荷兰语单词首字母))

如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.

Semaphore 的 PV 操作中的加减计数器操作都是原⼦的, 可以在多线程环境下直接使⽤.

代码示例:

//创建 Semaphore ⽰例, 初始化为 4, 表⽰有 4 个可⽤资源.
//acquire ⽅法表⽰申请资源(P操作), release ⽅法表⽰释放资源(V操作)
//创建 20 个线程, 每个线程都尝试申请资源, sleep 1秒之后, 释放资源. 观察程序的执⾏效果.
import java.util.concurrent.Semaphore;public class Test {public static void main(String[] args) {Semaphore semaphore = new Semaphore(4);Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("apply");try {semaphore.acquire();System.out.println("accussful");Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}semaphore.release();System.out.println("release");}};for (int i = 0; i < 20; i++) {Thread t = new Thread(runnable);t.start();}}
}
✏️3.5 CountDownLatch

同时等待 N 个任务执行结束

🌰例如 : 好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。

代码示例:

  1. 构造 CountDownLatch 实例, 初始化 10 表示有 10 个任务需要完成.
  2. 每个任务执行完毕, 都调⽤用countDown() . 在 CountDownLatch 内部的计数器同时自减.
  3. 主线程中使用 latch.await(); 阻塞等待所有任务执行完毕. 相当于计数器为 0 了.
import java.util.Random;
import java.util.concurrent.CountDownLatch;public class Test {public static void main(String[] args) throws InterruptedException {CountDownLatch count = new CountDownLatch(10);Random random = new Random();Runnable runnable = new Runnable() {@Overridepublic void run() {try {Thread.sleep(random.nextInt(5000));count.countDown();} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 10; i++) {Thread t = new Thread(runnable);t.start();}// 必须等到 10 ⼈全部回来count.await();System.out.println("game is over");}
}

📕4. synchronized 原理

首先 , 我们总结一下synchronized锁的基本特定 :

  1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.
  2. 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁.
  3. 实现轻量级锁的时候大概率用到自旋锁策略
  4. 是一种不公平锁
  5. 是一种可重入锁
  6. 不是读写锁
✏️4.1 加锁工作过程

JVM 将 synchronized 锁分为无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升级。
在这里插入图片描述

  1. 偏向锁

第一个尝试加锁的线程, 优先进入偏向锁状态.

偏向锁不是真的 “加锁”, 只是给对象头中做一个 “偏向锁的标记”, 记录这个锁属于哪个线程.如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销) , 如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进⼊一般的轻量级锁状态.

偏向锁本质上相当于 “延迟加锁” . 能不加锁就不加锁, 尽量来避免不必要的加锁开销 . 但是该做的标记还是得做的, 否则无法区分何时需要真正加锁.

🌰举个栗子理解偏向锁 :

假设男主是一个锁, 女主是一个线程. 如果只有这一个线程来使用这个锁, 那么男主女主即使不领证结婚(避免了高成本操作), 也可以一直幸福的生活下去.

但是女配出现了, 也尝试竞争男主, 此时不管领证结婚这个操作成本多高, 女主也势必要把这个动作完成了, 让女配死心

  1. 轻量级锁

随着其他线程进入竞争, 偏向锁状态被消除, 进入轻量级锁状态(自适应的自旋锁)

  1. 重量级锁

如果竞争进一步激烈, 自旋不能快速获取到锁状态, 就会膨胀为重量级锁

✏️4.2 其他的优化操作
  1. 锁消除

有些应用程序的代码中, 用到了 synchronized, 但其实没有在多线程环境下 , 编译器+JVM 会判断锁是否可消除. 如果可以, 就直接消除 , 避免白白浪费了一些资源开销.

  1. 锁粗化

一段逻辑中如果出现多次加锁解锁, 编译器 + JVM 会⾃动进行锁的粗化
在这里插入图片描述

🌰举个例子理解锁粗化

滑稽老哥当了领导, 给下属交代工作任务:

⽅式⼀:
打电话, 交代任务1, 挂电话.
打电话, 交代任务2, 挂电话.
打电话, 交代任务3, 挂电话

⽅式二:
打电话, 交代任务1, 任务2, 任务3, 挂电话.

明显方式二的效率更高

📕5. CAS

CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功
✏️5.1 CAS的ABA问题

在这里插入图片描述

ABA问题就好比 , 我们买⼀个手机, 无法判定这个手机是刚出厂的新手机, 还是别人用旧了, 又翻新过的手机.

相关文章:

JAVA SE 多线程(下)

文章目录 &#x1f4d5;1. 常见的锁策略✏️1.1 乐观锁VS悲观锁✏️1.2 轻量级锁VS重量级锁✏️1.3 自旋锁✏️1.4 公平锁VS非公平锁✏️1.5 可重入锁和不可重入锁✏️1.6 读写锁 &#x1f4d5;2. 死锁✏️2.1 哲学家就餐问题✏️2.2 形成死锁的必要条件✏️2.3 如何避免死锁 &…...

DeepSeek+Dify之九多模态大模型识别图片

Dify之八添加各种在线大模型 文章目录 背景整体流程测试数据用到的节点开始列表操作LLM结束实现步骤1、新建工作流2、开始节点3、列表操作4、LLM节点(多模态大模型检索)5、结束节点测试发布导出背景 有了上一篇文章的基础,现在可以了解下多模态大模型识别图片的工作流 整体…...

软件工程(七):MQTT协议

概念 特性描述协议类型应用层协议&#xff08;基于 TCP/IP&#xff09;通信模式发布/订阅&#xff08;Publish/Subscribe&#xff09;网络开销小报文头仅 2 字节起&#xff0c;非常轻量保持长连接使用 Keep Alive 机制&#xff0c;适合持续连接的设备面向物联网广泛用于智能家…...

网络 :网络基础【网络框架认识】

网络 &#xff1a;网络基础【对网络的认识】 &#xff08;一&#xff09;网络发展&#xff08;二&#xff09;协议1、协议是什么&#xff1f;2、协议分层3、OSI七层模型4、TCP/IP五层(或四层)模型5、系统与网络的关系6、总结 &#xff08;三&#xff09;网络传输流程1、网络传输…...

Kotlin 极简小抄 P8(不可空类型、可空类型、注意事项、非空断言 !!)

Kotlin 概述 Kotlin 由 JetBrains 开发&#xff0c;是一种在 JVM&#xff08;Java 虚拟机&#xff09;上运行的静态类型编程语言 Kotlin 旨在提高开发者的编码效率和安全性&#xff0c;同时保持与 Java 的高度互操作性 Kotlin 是 Android 应用开发的首选语言&#xff0c;也可…...

几种超声波芯片的特点和对比

一 CX20106A ZIP - 8 CX20106A ZIP - 8 的核心竞争力在于高性价比、易用性和抗光干扰能力&#xff0c;尤其适合消费电子、短距离工业检测和低成本物联网场景。尽管在距离和精度上不及高端芯片&#xff0c;但其成熟的电路方案和广泛的市场应用&#xff08;如经典红外遥控升级为超…...

软考 系统架构设计师系列知识点之杂项集萃(66)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之杂项集萃&#xff08;65&#xff09; 第106题 一般说来&#xff0c;SoC称为系统级芯片&#xff0c;也称片上系统&#xff0c;它是一个有专用目标的集成电路产品。以下关于SoC不正确的说法是&#xff08;&#xff09;。…...

Linux云计算训练营笔记day13[CentOS 7 find、vim、vimdiff、ping、wget、curl、RPM、YUM]]

Linux云计算训练营笔记day13[CentOS 7 find、vim、vimdiff、ping、wget、curl、RPM、YUM]] 目录 Linux云计算训练营笔记day13[CentOS 7 find、vim、vimdiff、ping、wget、curl、RPM、YUM]]1.find练习2.vim高级使用2.1 命令模式:2.2 插入模式:2.3 末行模式: 3. vimdiff4. ping5.…...

年会招标抽奖活动软件———仙盟创梦IDE

年会是企业一年的总结与欢庆时刻&#xff0c;而抽奖环节更是点燃全场气氛的关键。如何让抽奖环节既大气又充满仪式感&#xff1f;选对抽奖软件至关重要&#xff01;本文精心挑选了 3 款兼具实用性与氛围感的年会抽奖软件&#xff0c;从界面设计到功能特色&#xff0c;全方位为你…...

智防火灾,慧控能耗:物联网赋能金融行业电气安全革新

摘要 随着金融行业对电气安全需求的不断提升&#xff0c;传统用电管理模式已难以满足现代金融机构对火灾防控、能耗管理和智能运维的要求。本文基于物联网、云计算及大数据分析技术&#xff0c;提出一套针对金融行业的安全用电解决方案。该方案通过智能化硬件部署与平台化管理…...

UML基本概念:构造块、公共机制与规则

本篇来介绍UML的一些基础概念。 1 UML的结构 2 构造块 UML构造块是构成UML模型的基础元素&#xff0c;它们共同构成了UML的核心。 UML 的三种基本构造块是&#xff1a; 事物&#xff1a;对模型中重要元素的抽象&#xff0c;如类、对象等等关系&#xff1a;用来连接事物的&a…...

JAVA项目中常见的注解总结

以下是 Spring Boot/Spring MVC 项目中按使用频率排序的核心注解总结&#xff0c;包含常用场景和示例&#xff0c;帮助你快速掌握高频开发需求&#xff1a; 一、高频基础注解&#xff08;框架核心&#xff09; 1. SpringBootApplication&#xff08;启动类注解&#xff09; …...

数字电子技术基础(六十二)——使用Multisim软件绘制边沿触发的D触发器和JK触发器

1 使用Mulitism软件模拟时钟触发的D触发器 D触发器是一种基本的数字电路存储元件&#xff0c;它在时钟信号的边沿将输入数据D传递到输出Q。下面开始使用Multisim软件来模拟时钟触发的D触发器。 器件选择&#xff1a; 触发器选择&#xff1a;在组选项栏中点击Misc Digital&am…...

滚珠丝杆的承载力是多少?

滚珠丝杆的承载力不是一个固定值&#xff0c;它受到多种因素的影响&#xff0c;包括直径、螺距、滚珠个数、滚珠直径、材料、润滑条件等。 ‌滚珠丝杆的承载力主要取决于其额定动负荷和静负荷&#xff0c;额定动负荷是指在特定条件下&#xff0c;滚珠丝杆能够承受的动态负载&am…...

CQF预备知识:一、微积分简介 —— 基本术语详解

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。 &#x1f4d6; 数学入门全解 本教程为复习课程&#xff0c;旨在帮助读者复习数学知识。教程涵盖以下四个主题&#xff1a; 微积分线性代数微…...

【深度估计 Depth Estimation】数据集介绍

文章目录 总览1. KITTI Depth Completion1.1. 来源1.2. 采集场景1.3. 数据集内容1.4. 标注内容1.5. 任务目标1.6. 样本量1.7. 下载链接 2. KITTI Stereo Evaluation 20152.1. 来源2.2. 采集场景2.3. 数据集内容2.4. 标注内容2.5. 任务目标2.6. 样本量2.7. 下载链接 3. Virtual …...

鸿蒙UI开发——Builder与LocalBuilder对比

1、概 述 在ArkUI中&#xff0c;有的朋友应该接触过Builder和LocalBuilder。其中有了LocalBuilder的存在&#xff0c;是为了解决组件的父子关系和状态管理的父子关系保持一致的问题。 这里面最直观的表现则是this的指向问题与组件刷新问题&#xff0c;本文对Builder与LocalBu…...

FFT加窗和抽取滤波

FFT加窗 在信号处理中&#xff0c;为了减少频谱泄漏&#xff08;Spectral Leakage&#xff09;&#xff0c;在进行快速傅里叶变换&#xff08;FFT&#xff09;时通常会采用加窗&#xff08;Windowing&#xff09;技术。包括常见的窗函数及其特性对比。 MATLAB FFT 加窗流程 采…...

CentOS 10:启动telnet服务

参考&#xff0c; 鳥哥私房菜 - 第七章、網路安全與主機基本防護&#xff1a;限制埠口, 網路升級與 SELinux 7.3.3 埠口与服务的启动/关闭及开机时状态设定 我们知道系统的 Telnet 服务通常是以 super daemon 来控管的&#xff0c;请您启动您系统的 telnet 试看看。 1 要启动 …...

腾讯位置服务重构出行行业的技术底层逻辑

位置智能&#xff1a;重构出行行业的技术底层逻辑 在智慧城市建设与交通出行需求爆发的双重驱动下&#xff0c;位置服务正从工具层跃升为出行行业的核心基础设施。腾讯位置服务以“连接物理世界与数字空间”为核心理念&#xff0c;通过构建高精度定位、实时数据融合、智能决策…...

webpack5所用依赖以及对应的版本

所有依赖以及版本 {"name": "market-web","version": "0.1.0","private": true,"scripts": {"dev": "cross-env NODE_ENVdevelopment webpack serve --config ./vue.config.js","buil…...

vue-cli 构建打包优化(JeecgBoot-Vue2 配置优化篇)

项目&#xff1a;jeecgboot-Vue2 在项目二次开发后&#xff0c;在本人电脑打包时间为3分35秒左右 webpack5默认优化&#xff1a; Tree Shaking&#xff08;摇树优化&#xff09;&#xff1a;删除未使用的代码base64 内联&#xff1a; 小于 8KB 的资源&#xff08;图片等&…...

Webpack 分包策略详解及实现

Webpack 的分包策略&#xff08;Code Splitting&#xff09;是优化前端应用性能的重要手段&#xff0c;它能将代码拆分成多个 bundle&#xff0c;实现按需加载或并行加载&#xff0c;从而减少初始加载时间。 分包策略的必要性 在大型项目中&#xff0c;如果将所有代码打包到一…...

大模型微调与高效训练

随着预训练大模型&#xff08;如BERT、GPT、ViT、LLaMA、CLIP等&#xff09;的崛起&#xff0c;人工智能进入了一个新的范式&#xff1a;预训练-微调&#xff08;Pre-train, Fine-tune&#xff09;。这些大模型在海量数据上学习到了通用的、强大的表示能力和世界知识。然而&…...

postgreSQL日常维护

目录 登录数据库 数据库操作列出列出库 创建库 删除库 切换库 查看库的大小 数据表操作 列出表 创建表 复制表 删除表 查看表的结构 模式操作命令 创建模式 默认模式 删除模式 查看所有模式 在指定模式中创建表 切换当前模式 查看当前所在schema 查看搜索…...

数据直观分析与可视化

数据直观分析与可视化 一、数据的直观分析核心价值 数据的直观分析旨在通过视觉化的方式&#xff0c;帮助人们更直观、更快速地理解数据的特征和模式&#xff0c;从而发现趋势、异常值、分布情况以及变量之间的关系&#xff0c;为决策提供支持。 数据可视化与信息图形、信息可…...

BeamDojo: Learning Agile Humanoid Locomotion on Sparse Footholds

BeamDojo: Learning Agile Humanoid Locomotion on Sparse Footholds 研究动机解决方案技术路线踏脚点奖励双Critic进行稀疏奖励学习两阶段学习地形感知运动马尔可夫决策空间Sim2Real 附录实验结果 BeamDojo: Learning Agile Humanoid Locomotion on Sparse Footholds 研究动机…...

Spark大数据分与实践笔记(第五章 HBase分布式数据库-02)

文章目录 每日一句正能量第五章 HBase分布式数据库章节概要5.2 HBase的集群部署 每日一句正能量 人有三样东西是无法隐瞒的&#xff0c;咳嗽&#xff0c;穷困和爱&#xff0c;你想隐瞒越欲盖弥彰。人有三样东西是不该挥霍的&#xff0c;身体&#xff0c;金钱和爱&#xff0c;你…...

【面经分享】微派网络一面

HashMap 如何解决 哈希冲突&#xff1f; HashMap 是拉链法解决。 发生哈希冲突时&#xff0c;同一个槽位上&#xff0c;会形成一个链表。 一个槽位上的节点达到树化的阈值后&#xff0c;会树化为红黑树。 拉链法&#xff1a;哈希冲突时&#xff0c;同一个哈希槽拉成一个链表…...

【寻找Linux的奥秘】第七章:虚拟地址空间

前言 本专题将基于Linux操作系统来带领大家学习操作系统方面的知识以及学习使用Linux操作系统。上一章我们简单认识了环境变量&#xff0c;本章将讲解操作系统中另一个重要的概念——程序地址空间。 1. 初步认识 之前在我们学习C语言和C时我们知道&#xff0c;在我们的程序中不…...

网络安全-等级保护(等保) 2-0 等级保护制度现行技术标准

################################################################################ 第二章&#xff1a;现行等保标准要求&#xff0c;通过表格方式详细拆分了等保的相关要求。 GB 17859-1999 计算机信息系统 安全保护等级划分准则【现行】 GB/T22240-2020 《信息安全技术…...

Linux:进程信号---信号的保存与处理

文章目录 1. 信号的保存1.1 信号的状态管理 2. 信号的处理2.1 用户态与内核态2.2 信号处理和捕捉的内核原理2.3 sigaction函数 3. 可重入函数4. Volatile5. SIGCHLD信号 序&#xff1a;在上一章中&#xff0c;我们对信号的概念及其识别的底层原理有了一定认识&#xff0c;也知道…...

【Linux】C语言模拟实现shell命令行(程序替换原理)

目录 一、自动化构建工具&#xff08;makefile&#xff09; 二、输出提示符 三、获取用户输入的数据 四、将用户输入的指令字符串进行分割&#xff1a; 五、执行用户输入的命令 六、发现cd命令用不了&#xff08;内建命令&#xff09; 原因在于&#xff1a; 七、处理内…...

WordPress Madara插件存在文件包含漏洞(CVE-2025-4524)

免责声明 本文档所述漏洞详情及复现方法仅限用于合法授权的安全研究和学术教育用途。任何个人或组织不得利用本文内容从事未经许可的渗透测试、网络攻击或其他违法行为。使用者应确保其行为符合相关法律法规,并取得目标系统的明确授权。 对于因不当使用本文信息而造成的任何直…...

【Java】泛型在 Java 中是怎样实现的?

先说结论 , Java 的泛型是伪泛型 , 在运行期间不存在泛型的概念 , 泛型在 Java 中是 编译检查 运行强转 实现的 泛型是指 允许在定义类 , 接口和方法时使用的类型参数 , 使得代码可以在不指定具体类型的情况下操作不同的数据类型 , 从而实现类型安全的代码复用 的语言机制 . …...

Lambda表达式的高级用法

今天来分享下Java的Lambda表达式&#xff0c;以及它的高级用法。 使用它可以提高代码的简洁度&#xff0c;使代码更优雅。 一、什么是lambda表达式 Lambda 表达式是 Java 8 引入的特性&#xff0c;用于简化匿名内部类的语法&#xff0c;使代码更简洁&#xff0c;尤其在处理函…...

ctfhub技能书http协议

http://challenge-ffe8afcf1a75b867.sandbox.ctfhub.com:10800/index.php curl -v -X CTFHUB http://challenge-ffe8afcf1a75b867.sandbox.ctfhub.com:10800/index.php curl&#xff1a;用于发送 HTTP 请求的命令行工具。 -v&#xff08;--verbose&#xff09;&#xff1a;开启…...

面试题 - 微服务相关的经典问题(33道)

1.什么是微服务&#xff1f; 微服务&#xff08;Microservices&#xff09;是一种软件架构风格&#xff0c;将一个大型应用程序划分为一组小型、自治且松耦合的服务。每个微服务负责执行特定的业务功能&#xff0c;并通过轻量级通信机制&#xff08;如HTTP&#xff09;相互协作…...

在C#中对List<T>实现多属性排序

本文介绍了四种实现多级排序的方法&#xff1a;1. LINQ链式调用&#xff1a;使用OrderBy和ThenBy实现多级排序&#xff0c;直观易读&#xff0c;适合动态需求&#xff0c;返回新列表。2. 自定义比较器&#xff08;IComparer&#xff09;&#xff1a;适用于复杂或高频排序&#…...

C++初阶-vector的模拟实现3

目录 1.预备知识&#xff1a;initializer_list 1.1初步了解 1.2关于initializer_list的deepseek的回答 C中的 std::initializer_list 主要特性 常见用途 1. 接受列表的构造函数和函数 2. 基于范围的 for 循环 重要注意事项 实现示例 2.vector::vector(initializer_li…...

详解鸿蒙仓颉开发语言中的日志打印问题

一门新的开发语言在诞生初期&#xff0c;由于它本身的特性和使用人数暂时较少&#xff0c;会容易出现一些大家不太容易理解的问题&#xff0c;或者说有一些坑。今天就详细分享一下仓颉开发语言中的日志打印相关内容&#xff0c;带大家踩一踩坑。 AppLog 在新创建的项目中&…...

dify基于文本模型实现微调Fine-tune语料构造工作流

主要是分为5个部分。分别是&#xff1a;开始、文档提取器、代码执行、LLM大语言模型、结束 5个部分 打开dify&#xff0c;创建一个空白页面-选择工作流&#xff0c;我们给应用起个名字。 创建完成后&#xff0c;进入工作流画布界面 开始 在开始节点中新建2个输入参数。1个是用…...

手机充电协议

1、手机快充 公有&#xff1a;PD、QC(高通骁龙芯片&#xff09; 私有&#xff1a; 华为&#xff1a;FCP&#xff08;fast charge protocol) 、SCP( super charge protocol) 、 小米&#xff1a; Mi Turbo Charge oppo&#xff1a;VOOC/SuperVOOC vivo&#xff1a;FlashCharge、…...

HarmonyOS 应用开发,如何引入 Golang 编译的第三方 SO 库

本指南基于笔者临时修复的 ohos_golang_go 项目fork&#xff0c;解决HO 应用导入 cgo编译产物时的 crash 问题。 1. 下载 ohos_golang_go git clone https://gitcode.com/deslord/ohos_golang_go.git&#x1f4cc; 该仓库为笔者临时修复版本&#xff0c;修复了 CGO 编译模式下…...

polarctf-web-[某函数的复仇]

考点&#xff1a; 匿名构造函数(create_function) 题目来源&#xff1a;polarctf-web-[某函数的复仇] 解题&#xff1a; 代码审计&#xff1a; <?phphighlight_file(__FILE__);//flag:/flagif(isset($_POST[shaw])){$shaw $_POST[shaw];$root $_GET[root];if(preg_mat…...

Node.js Express 项目现代化打包部署全指南

Node.js Express 项目现代化打包部署全指南 一、项目准备阶段 1.1 依赖管理优化 # 生产依赖安装&#xff08;示例&#xff09; npm install express mongoose dotenv compression helmet# 开发依赖安装 npm install nodemon eslint types/node --save-dev1.2 环境变量配置 /…...

华为云Flexus+DeepSeek征文|Flexus云服务器Dify-LLM资源部署极致体验Agent

前引&#xff1a;重磅来袭&#xff01;本次以DeepSeek-V3/R1商用大模型和Dify-LLM应用平台一键部署为核心&#xff0c;专为新手打造“开箱即用”的AI开发体验。无论你是想快速搭建企业级AI应用&#xff0c;还是探索大模型落地的无限可能&#xff0c;只需跟随小编实现三步走&…...

Java详解LeetCode 热题 100(18):LeetCode 73. 矩阵置零(Set Matrix Zeroes)详解

文章目录 1. 题目描述2. 理解题目3. 解法一&#xff1a;使用两个额外数组标记法3.1 思路3.2 Java代码实现3.3 代码详解3.4 复杂度分析3.5 适用场景 4. 解法二&#xff1a;使用矩阵的第一行和第一列作为标记4.1 思路4.2 Java代码实现4.3 代码详解4.4 复杂度分析4.5 适用场景 5. …...

MySQL刷题 Day08

LC 1341电影评分 本题思路简单&#xff0c;但一不注意就错了 &#xff1a; 不难想到用union&#xff0c;写出如下代码&#xff1a; (select u.name results from MovieRating mr left join Users u on mr.user_id u.user_id group by mr.user_id order by count(mr.user_id…...

linux查看本机服务器的外网IP命令

在 Linux 中查看本机服务器的外网 IP&#xff08;公网 IP&#xff09;可以通过以下几种方法&#xff1a; 1. 使用 curl 查询外部服务&#xff08;推荐&#xff09; curl ifconfig.me或&#xff1a; curl icanhazip.com或&#xff1a; curl ipinfo.io/ip这些服务会返回你的公…...