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

并发编程---多线程不安全示例以及解决,多线程创建方式

文章目录

  • 并发
  • 并行
  • 多线程
  • 为什么需要多线程
  • 线程不安全示例
  • 并发出现问题的根源: 并发三要素
    • 可见性: CPU 缓存引起
    • 原子性:分时复用引起
    • 有序性: 重排序引起
  • 线程不安全示例的解决方法
    • 使用AtomicLong类
    • 使用synchronized 关键字
  • 改进代码避免不必要的延迟
    • join()方法
      • 为什么使用 `join()`?
    • CountDownLatch类
      • 基本原理:
      • 常用方法:
  • 通过实现Runnable接口完成线程
  • 通过继承Thread类完成线程
  • 通过实现Callable接口完成接口
    • 手动创建线程,**使用 `FutureTask` 来处理 `Callable` 任务**
    • 使用线程池


并发

并发指的是系统在同一时间段内处理多个任务的能力,但并不要求这些任务同时执行。它侧重于任务的 逻辑上的同时进行,即在某个时间段内,系统切换多个任务的执行,这样看起来就像是多个任务“同时”运行,但实际上是在有限的资源上交替执行。

并发的特点

  • 逻辑上的同时:多个任务在时间上交替进行,给人一种“同时”执行的错觉,但它们通常是在一个处理器上通过快速切换执行的。
  • 时间分片:系统通过操作系统的调度器(例如时间片轮转)让任务看起来是并行的。
  • 可能不是物理同时执行:在单核 CPU 上,多个任务并发执行,但它们并不会真正同时执行。它们通过时间片的轮流执行来实现“并发”。

并发的例子

  • 单核 CPU 上运行多个任务。任务 A 和任务 B 不会同时执行,但它们会交替执行,任务 A 一会儿执行,接着任务 B 执行。
  • 例如,一个网站需要处理多个用户的请求,尽管每次只有一个请求在处理,但多个请求是并发的,因为它们会在时间上交替被处理。

并行

并行是指多个任务 同时 在多个处理器(或多个核心)上执行。它是并发的一种特殊情况,但必须具备 多核处理器多处理器系统 的支持,允许任务真正地同时执行。

并行的特点

  • 物理上的同时执行:多个任务在多个处理器或处理器核心上同时执行,任务间不需要交替执行。
  • 需要多核或多 CPU:并行计算通常依赖于硬件支持,例如多核 CPU 或分布式计算系统。
  • 速度更快:由于任务真正同时执行,通常比并发要高效,尤其是对于计算密集型任务。

并行的例子

  • 多核 CPU 上的并行任务。任务 A 和任务 B 可以在不同的核心上同时执行,不需要交替执行。
  • 例如,图像处理程序可以将图像划分为多个部分,然后在不同的处理器核心上同时处理每个部分,从而大大加快处理速度。

多线程

多线程是并发编程的一种方式,指的是一个程序内部可以有多个线程,这些线程共享程序的资源(如内存、文件描述符等),并且可以并行或并发地执行。每个线程都是操作系统调度的基本单位。

多线程的特点

  • 多线程是实现并发的一种方式,具体指的是在同一个进程中,通过线程来执行多个任务。
  • 在支持多核处理器的机器上,线程可以在多个核心上同时执行,这就实现了真正的并行。
  • 线程之间共享内存,因此可以快速地交换信息,但也会带来线程安全的问题。

为什么需要多线程

众所周知,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:

  • CPU 增加了缓存,以均衡与内存的速度差异;// 导致 可见性 问题
  • 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致 原子性 问题
  • 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致 有序性 问题

线程不安全示例

如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。

这段代码演示了 10000 个线程并发执行计算任务,每个线程计算从 0100 的累加和并将结果加到共享变量 ThreadLearn.result 中。操作结束之后它的值有可能小于 50500000。

package thread;public class ThreadLearn {public static long result; // 多个线程共享的,用于存储最终的计算结果public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) { // 通过for循环启动10000个线程,每个线程通过MyThread类来执行任务MyThread myThread = new MyThread(100); // 每个线程都会计算从0到100的和// 启动线程,让线程并发执行Thread thread = new Thread(myThread);thread.start(); // start() 会异步调用run()方法}Thread.sleep(10*1000);System.out.println(result);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}ThreadLearn.result += sum; // 将每个线程的sum累加到result变量中。}
}
49833400 // 结果总是小于50500000

并发出现问题的根源: 并发三要素

上述代码输出为什么不是 50500000? 并发出现问题的根源是什么?

可见性: CPU 缓存引起

可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到。

举个简单的例子,看下面这段代码:

//线程1执行的代码
int i = 0;
i = 10;//线程2执行的代码
j = i;

假若执行线程 1 的是 CPU1,执行线程 2 的是 CPU2。由上面的分析可知,当线程 1 执行 i = 10 这句时,会先把 i 的初始值加载到 CPU1 的高速缓存中,然后赋值为 10,那么在 CPU1 的高速缓存当中 i 的值变为 10 了,却没有立即写入到主存当中。

此时线程 2 执行 j = i,它会先去主存读取 i 的值并 i 加载到 CPU2 的缓存当中,注意此时内存当中 i 的值还是 0,i 那么就会使得 j 的值为 0,而不是 10.

这就是可见性问题,线程 1 对变量 i 修改了之后,线程 2 没有立即看到线程 1 修改的值。

原子性:分时复用引起

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

举个简单的例子,看下面这段代码:

int i = 1;// 线程1执行
i += 1;// 线程2执行
i += 1;

这里需要注意的是:i += 1 需要三条 CPU 指令

  1. 将变量 i 从内存读取到 CPU 寄存器;
  2. 在 CPU 寄存器中执行 i + 1 操作;
  3. 将最后的结果 i 写入内存(缓存机制导致可能写入的是 CPU 缓存而不是内存)。

由于 CPU 分时复用(线程切换)的存在,线程 1 执行了第一条指令后,就切换到线程 2 执行,假如线程 2 执行了这三条指令后,再切换会线程 1 执行后续两条指令,将造成最后写到内存中的 i 值是 2 而不是 3。

有序性: 重排序引起

有序性:即程序执行的顺序按照代码的先后顺序执行。举个简单的例子,看下面这段代码:

int a = 1; // 指令1
int b = 2; // 指令2

编译器可能会将其重排序为:

int b = 2; // 指令2
int a = 1; // 指令1

如果这些变量是共享变量,且多个线程依赖它们的顺序,就会导致问题。

线程不安全示例的解决方法

使用AtomicLong类

package thread;public class ThreadLearn {// AtomicLong 是 Java 提供的一个类,用于处理长整型(long)数据类型的原子操作public static AtomicLong result = new AtomicLong(0);public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) { // 通过for循环启动10000个线程,每个线程通过MyThread类来执行任务MyThread myThread = new MyThread(100); // 每个线程都会计算从0到100的和// 启动线程,让线程并发执行Thread thread = new Thread(myThread);thread.start(); // start() 会异步调用run()方法}Thread.sleep(10*1000);System.out.println(result);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}ThreadLearn.result.addAndGet(sum);// AtomicLong 提供了原子操作 addAndGet(),这个方法将 sum 加到 result 上,并返回更新后的值。}
}
50500000

AtomicLong 的作用: 使用 AtomicLongaddAndGet(sum) 方法,能够保证在多线程环境下,对 result 的累加操作是 原子 的,即使多个线程同时执行相同操作,也能保证每次操作都是安全的,不会产生竞争条件。

使用synchronized 关键字

package thread;public class ThreadLearn {public static long result; // 多个线程共享的,用于存储最终的计算结果public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) { // 通过for循环启动10000个线程,每个线程通过MyThread类来执行任务MyThread myThread = new MyThread(100); // 每个线程都会计算从0到100的和// 启动线程,让线程并发执行Thread thread = new Thread(myThread);thread.start(); // start() 会异步调用run()方法}Thread.sleep(10*1000);System.out.println(result);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}synchronized (ThreadLearn.class) {// synchronized关键字来保证同一时刻只有一个线程可以更新result的值// 通过加锁ThreadLearn.class(即类锁),使得对 result 的修改是线程安全的。ThreadLearn.result += sum;}}
}

synchronized 关键字确保同一时刻只有一个线程能进入被 synchronized 修饰的代码块。当一个线程正在执行被加锁的代码时,其他线程会被阻塞,直到该线程执行完毕并释放锁。这就避免了多个线程在同一时刻访问共享资源的情况,确保了对共享资源的修改是有序的,避免了数据竞争。

在这个代码中的使用

synchronized (ThreadLearn.class) {ThreadLearn.result += sum;
}

加锁对象synchronized 关键字后面跟的 ThreadLearn.class 表示加锁的是 ThreadLearn 类的对象锁。也就是说,所有线程在执行这个同步代码块时,必须先获取到 ThreadLearn 类的锁,才能执行 result += sum 操作。

保证顺序执行:通过加锁,当多个线程同时执行 run() 方法时,只有一个线程能进入 synchronized 块并修改 result。其他线程必须等待该线程执行完毕并释放锁后,才能继续修改 result。这样就保证了 result 的更新操作不会被多个线程同时执行,避免了并发问题。

改进代码避免不必要的延迟

join()方法

使用 join() 方法来确保所有线程执行完毕后再继续执行主线程,可以避免通过 Thread.sleep() 等待线程执行的潜在问题。以下是改进后的代码,使用了 join() 来确保主线程等待所有子线程执行完毕后再输出最终结果。

package thread;import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 提供原子操作public static AtomicLong result = new AtomicLong(0);public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis(); // 计时器int threadCount = 10000;Thread[] threads = new Thread[threadCount]; // 创建线程数组// 创建并启动 10000 个线程for (int i = 0; i < threadCount; i++) {MyThread myThread = new MyThread(100);threads[i] = new Thread(myThread);threads[i].start(); // 启动线程}// 使用 join() 等待所有线程执行完成for (Thread thread : threads) {thread.join(); // 确保主线程等待所有子线程执行完毕}// 打印最终结果和执行时间System.out.println(result);System.out.println("耗时:" + (System.currentTimeMillis() - start));}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i; // 计算 0 到 count 的和}ThreadLearn.result.addAndGet(sum);}
}

thread.join()join() 方法用于让主线程等待每个子线程执行完毕。主线程会依次调用 join(),直到所有的子线程执行完成。这样可以确保 result 输出的值是正确的。

为什么使用 join()

  • 等待所有线程完成join() 会让主线程等待当前线程完成。在这种情况下,主线程会等所有子线程执行完毕,避免提前输出结果。
  • 防止线程还没执行完毕时输出结果:如果没有使用 join(),主线程可能会在子线程执行之前就打印出结果,从而导致 result 的值不准确。

CountDownLatch类

package thread;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 提供原子操作public static AtomicLong result = new AtomicLong(0);static int count = 10000;  // 线程数public static CountDownLatch countDownLatch = new CountDownLatch(count);  // 初始化计数器public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis(); // 计时器Thread[] threads = new Thread[count]; // 创建线程数组,大小为 count// 创建并启动 10000 个线程for (int i = 0; i < count; i++) {MyThread myThread = new MyThread(100);  // 每个线程都会计算 0 到 100 的和threads[i] = new Thread(myThread);threads[i].start();  // 启动线程}// 等待所有线程完成countDownLatch.await(); // 主线程会在这里等待,直到计数器为 0// 打印最终结果和执行时间System.out.println(result);System.out.println("耗时:" + (System.currentTimeMillis() - start));}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;// 计算 0 到 count 的和for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 提供的原子操作进行更新ThreadLearn.result.addAndGet(sum);  // 线程安全的累加// 完成任务,计数器减 1ThreadLearn.countDownLatch.countDown();}
}

基本原理:

CountDownLatch 通过一个计数器来控制线程的等待,计数器的初始值是一个正整数。每当一个线程完成了某项工作,它会调用 countDown() 方法将计数器减 1。当计数器值减到 0 时,所有等待的线程(调用了 await() 方法的线程)都会被唤醒。

常用方法:

  • countDown():将计数器的值减 1。当计数器值为 0 时,所有等待的线程会被唤醒。
  • await():使当前线程等待,直到计数器的值为 0。该方法会阻塞当前线程,直到其他线程调用 countDown() 并使计数器减为 0。

通过实现Runnable接口完成线程

package thread;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 提供原子操作public static AtomicLong result = new AtomicLong(0);static int count = 10000;  // 线程数public static CountDownLatch countDownLatch = new CountDownLatch(count);  // 初始化计数器public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis(); // 计时器Thread[] threads = new Thread[count]; // 创建线程数组,大小为 count// 创建并启动 10000 个线程for (int i = 0; i < count; i++) {MyThread myThread = new MyThread(100);  // 每个线程都会计算 0 到 100 的和threads[i] = new Thread(myThread);threads[i].start();  // 启动线程}// 等待所有线程完成countDownLatch.await(); // 主线程会在这里等待,直到计数器为 0// 打印最终结果和执行时间System.out.println(result);System.out.println("耗时:" + (System.currentTimeMillis() - start));}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;// 计算 0 到 count 的和for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 提供的原子操作进行更新ThreadLearn.result.addAndGet(sum);  // 线程安全的累加// 完成任务,计数器减 1ThreadLearn.countDownLatch.countDown();}
}

通过继承Thread类完成线程

public class ThreadLearn {// 使用 AtomicLong 提供原子操作public static AtomicLong result = new AtomicLong(0);static int count = 10000;  // 线程数public static CountDownLatch countDownLatch = new CountDownLatch(count);  // 初始化计数器public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis(); // 计时器Thread[] threads = new Thread[count]; // 创建线程数组,大小为 count// 创建并启动 10000 个线程for (int i = 0; i < count; i++) {MyThread1 myThread = new MyThread1(100);  // 每个线程都会计算 0 到 100 的和threads[i] = new Thread(myThread);threads[i].start();  // 启动线程}// 等待所有线程完成countDownLatch.await(); // 主线程会在这里等待,直到计数器为 0// 打印最终结果和执行时间System.out.println(result);System.out.println("耗时:" + (System.currentTimeMillis() - start));}
}class MyThread1 extends Thread {private long count;public MyThread1(int count) {this.count = count;}@Overridepublic void run() {super.run();int sum = 0;// 计算 0 到 count 的和for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 提供的原子操作进行更新ThreadLearn.result.addAndGet(sum);  // 线程安全的累加// 完成任务,计数器减 1ThreadLearn.countDownLatch.countDown();}
}

通过实现Callable接口完成接口

手动创建线程,使用 FutureTask 来处理 Callable 任务

public class ThreadLearn {// 使用 AtomicLong 提供原子操作public static AtomicLong result = new AtomicLong(0);static int count = 10000;  // 线程数public static CountDownLatch countDownLatch = new CountDownLatch(count);  // 初始化计数器public static void main(String[] args) throws InterruptedException, ExecutionException {FutureTask<Integer> future = new FutureTask<Integer>(new MyThread2(100));Thread thread = new Thread(future);thread.start();Integer o = future.get();System.out.println(o);}
}class MyThread2 implements Callable<Integer> {private int count;public MyThread2(int count) {this.count = count;}@Overridepublic Integer call() throws Exception {int sum = 0;// 计算 0 到 count 的和for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 提供的原子操作进行更新ThreadLearn.result.addAndGet(sum);  // 线程安全的累加// 完成任务,计数器减 1ThreadLearn.countDownLatch.countDown();return sum;}
}

使用线程池

public class ThreadLearn {// 使用 AtomicLong 提供原子操作public static AtomicLong result = new AtomicLong(0);public static CountDownLatch countDownLatch = new CountDownLatch(count);  // 初始化计数器public static ExecutorService executor = Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException, ExecutionException {Future<Integer> submit = executor.submit(new MyThread2(100));Integer i = submit.get();System.out.println(i);}
}class MyThread2 implements Callable<Integer> {private int count;public MyThread2(int count) {this.count = count;}@Overridepublic Integer call() throws Exception {int sum = 0;// 计算 0 到 count 的和for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 提供的原子操作进行更新ThreadLearn.result.addAndGet(sum);  // 线程安全的累加// 完成任务,计数器减 1ThreadLearn.countDownLatch.countDown();return sum;}
}

相关文章:

并发编程---多线程不安全示例以及解决,多线程创建方式

文章目录 并发并行多线程为什么需要多线程线程不安全示例并发出现问题的根源: 并发三要素可见性: CPU 缓存引起原子性&#xff1a;分时复用引起有序性: 重排序引起 线程不安全示例的解决方法使用AtomicLong类使用synchronized 关键字 改进代码避免不必要的延迟join()方法为什么…...

多模态模型详解

多模态模型是什么 多模态模型是一种能够处理和理解多种数据类型&#xff08;如文本、图像、音频、视频等&#xff09;的机器学习模型&#xff0c;通过融合不同模态的信息来提升任务的性能。其核心在于利用不同模态之间的互补性&#xff0c;增强模型的鲁棒性和准确性。 如何融合…...

从零到一:开发并上线一款极简记账本小程序的完整流程

从零到一&#xff1a;开发并上线一款极简记账本小程序的完整流程 目录 前言需求分析与功能设计 2.1 目标用户分析2.2 核心功能设计2.3 技术栈选择 开发环境搭建 3.1 微信开发者工具安装与配置3.2 项目初始化3.3 版本控制与协作工具 前端开发 4.1 页面结构与布局4.2 组件化开发…...

更加通用的Hexo多端部署原理及实现,适用于各种系统之间

本文推荐在作者的个人博客网站阅读&#xff1a;shenying.online 一、故事背景 故事发生在大学上学期间&#xff08;而不是寒假&#xff09;。上学期间&#xff0c;宿舍条件极其恶劣&#xff0c;半夜断电、空间狭小。我们大学垃圾条件使用游戏本的种种弊端被无限放大&#xff1…...

5g基站测试要求和关键点

5G基站的测试要求涉及多个方面&#xff0c;以确保其性能、覆盖能力、稳定性和合规性。以下是5G基站测试的主要要求和关键点&#xff1a; 一、基础性能测试 射频&#xff08;RF&#xff09;性能测试 发射机性能&#xff1a;验证基站的发射功率、频率误差、调制质量&#xff08;E…...

算法——搜索算法:原理、类型与实战应用

搜索算法&#xff1a;开启高效信息检索的钥匙 在信息爆炸的时代&#xff0c;搜索算法无疑是计算机科学领域中熠熠生辉的存在&#xff0c;它就像一把神奇的钥匙&#xff0c;为我们打开了高效信息检索的大门。无论是在日常生活中&#xff0c;还是在专业的工作场景里&#xff0c;…...

PlantUML 总结

PlantUML 总结 1. 概述 PlantUML 是一个开源工具&#xff0c;允许用户通过简单的文本描述来生成各种UML图表。它支持多种图表类型&#xff0c;包括但不限于序列图、用例图、类图、活动图等。 2. 基本概念 2.1 开始和结束标记 startuml 和 enduml&#xff1a;用于标记Plant…...

C++ 相对的字符串,判断却不相对

一、场景 在做项目的时候&#xff0c;有这样一个场景&#xff0c;根据字符串名称&#xff0c;给对应的变量赋值。传递的字符串跟对比的字符串是一样的&#xff0c;判断的时候却不相等&#xff0c;导致变量未正确附上值。 二、解决 经过查找&#xff0c;发现是字符串编码的问题…...

【嵌入式Linux应用开发基础】open函数与close函数

目录 一、open函数 1.1. 函数原型 1.2 参数说明 1.3 返回值 1.4. 示例代码 二、close函数 2.1. 函数原型 2.2. 示例代码 三、关键注意事项 3.1. 资源管理与泄漏防范 3.2. 错误处理的严谨性 3.3. 标志&#xff08;flags&#xff09;与权限&#xff08;mode&#xff…...

在实体机和wsl2中安装docker、使用GPU

正常使用docker和gpu&#xff0c;直接命令行安装dcoker和&#xff0c;nvidia-container-toolkit。区别在于&#xff0c;后者在于安装驱动已经cuda加速时存在系统上的差异。 1、安装gpu驱动 在实体机中&#xff0c;安装cuda加速包&#xff0c;我们直接安装 driver 和 cuda 即可…...

Unity3D实现显示模型线框(shader)

系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、效果展示👉二、第一种方式👉二、第二种方式👉壁纸分享👉总结👉前言 在 Unity 中显示物体线框主要基于图形渲染管线和特定的渲染模式。 要显示物体的线框,通常有两种常见的方法:一种是利用内置的渲染…...

解释和对比“application/octet-stream“与“application/x-protobuf“

介绍 在现代 Web 和分布式系统的开发中&#xff0c;数据的传输和交换格式扮演着关键角色。为了确保数据在不同系统之间的传输过程中保持一致性&#xff0c;MIME 类型&#xff08;Multipurpose Internet Mail Extensions&#xff09;被广泛应用于描述数据的格式和内容类型。在 …...

VAD端到端系列梳理以及阅读

0. 简介 最近VAD v2论文出来了&#xff0c;又掀起了一波该系列模型的热点。我们先看一下蒋博的文章&#xff0c;然后再来看一下《VADv2: End-to-End Vectorized Autonomous Driving via Probabilistic Planning》这篇文章&#xff0c;代码目前还没开源&#xff0c;可以期待一波…...

MySQL中类似PostgreSQL中的string_agg函数--GROUP_CONCAT函数的使用

文章目录 结论&#xff1a;MySQL没有string_agg&#xff0c;但有GROUP_CONCATGROUP_CONCAT函数的基本用法示例注意事项 系统变量 group_concat_max_len 如何查看和设置查看当前的group_concat_max_len值设置group_concat_max_len值 相关源码相关链接 结论&#xff1a;MySQL没有…...

在vmd中如何渲染透明水分子

1.设置背景为白色 依次点击Graphics>>Colors... 2. 改变渲染模式 依次点击Display>>rendermode>>GLSL 3. 渲染水分子 选中水分子&#xff0c;显色方式改为ColorID, 编号10的颜色&#xff1b; 选择材质为GlassBubble; 绘图方式为QuickSurf. 若水盒子显示效…...

每日十题八股-补充材料-2025年2月12日

1.从输入URL到页面展示发生了什么&#xff1f; 每日十题八股-2025年1月6日-8&#xff08;整体的回答&#xff09; 解析URL&#xff08;判断URL合不合法&#xff0c;不合法直接跳转搜索引擎进行搜索&#xff09;。判断浏览器中是否有缓存&#xff0c;有缓存则直接返回。获得IP…...

springboot+mybatis进行普通事务操作transaction

文章目录 背景前置环境准备关于 configuration 代码关于 transaction 使用其他 背景 你使用 springboot 和 mybatis/mybatis plus 来进行 web 开发&#xff0c;但是你发现你需要使用到事务操作 前置环境准备 首先你得在 application.yml 中配置好 mysql 数据源&#xff0c;这…...

DeepSeek-R1技术革命:用强化学习重塑大语言模型的推理能力

引言&#xff1a;低成本高性能的AI新范式 在2025年1月&#xff0c;中国AI公司DeepSeek发布了两个标志性模型——DeepSeek-R1-Zero与DeepSeek-R1&#xff0c;以仅600万美元的训练成本实现了与OpenAI O1系列&#xff08;开发成本约5亿美元&#xff09;相当的推理性能&#xff0c…...

MybatisPlus常用增删改查

记录下MybatisPlus的简单的增删改查 接口概述 Service和Mapper区别 Mapper简化了单表的sql操作步骤&#xff08;CRUD&#xff09;&#xff0c;而Serivce则是对Mapper的功能增强。 Service虽然加入了数据库的操作&#xff0c;但还是以业务功能为主&#xff0c;而更加复杂的SQL…...

常用电路(过压保护、电流/电压采集)

过压保护电路 输入电压使用电源&#xff08;36V&#xff09;或者typec&#xff08;20V&#xff09;&#xff0c;需要过压保护电路处理输入再连接到CH224K&#xff0c;保证输入不高于最大获取电压20V MOS管导通条件为栅源极有压差&#xff0c;一般为5-10V 三极管导通条件为基极…...

干部监督系统“三色”预警的构建与应用

在新时代背景下&#xff0c;强化干部监督、提升管理水平已成为推动国家治理体系和治理能力现代化的关键一环。干部监督系统“三色”预警机制作为一种创新的管理工具&#xff0c;通过智能化、可视化的手段&#xff0c;实现了对干部行为的高效管理。本文将详细探讨干部监督系统“…...

XML DOM

XML DOM XML DOM(Document Object Model)是一种用于访问和操作XML文档的标准方式。它提供了一种树形结构来表示XML文档,使得开发者能够方便地对XML数据进行读取、修改和操作。本文将详细介绍XML DOM的基本概念、结构、操作方法以及应用场景。 一、XML DOM的基本概念 XML …...

Zabbix-Trigger中的time函数坑

问题描述 由于功能需求&#xff0c;需要限制trigger的报警时间&#xff0c;所以加了如下的报警限制 and (time()>010000 and time()<045959)但是事与愿违&#xff0c;报警的时间总是对不上 但是&#xff0c;Zabbix设置的时区就是北京时间&#xff0c;应该是没有问题的…...

wordpress主题设置教程

然后你要制作好你的网站所有页面的静态页 都做好后&#xff0c;就可以开始制作主题了 第一步&#xff1a;建立你的主题标记 1、新建一个style.css&#xff0c;放在你的主题包根目录下&#xff0c;内容如下&#xff1a; /* Theme Name: 你的主题名称&#xff0c;随便起 Theme…...

9 数据流图

9 数据流图 9.1数据平衡原则 子图缺少处理后的数据操作结果返回前端应用以及后端数据库返回操作结果到数据管理中间件。 9.2解题技巧 实件名 存储名 加工名 数据流...

python项目相关

遇到的问题 解决 Python 模块导入路径问题 问题描述 在运行 Python 文件时&#xff0c;可能会遇到以下错误&#xff1a; ModuleNotFoundError: No module named utils原因&#xff1a; Python 的模块导入机制依赖于当前工作目录和 sys.path 中的路径。当直接运行某个文件时…...

基于轨道角动量自由度在空间频域中的可选择特性

将光的轨道角动量自由度应用到全息领域&#xff0c;证实了轨道角动量全息&#xff1b;实现了高维轨道角动量复用全息技术&#xff0c;获得了高安全的全息加密和超高容量全息信息系统。 1、轨道角动量自由度在全息中的引入 如图1所示&#xff0c;当全息图中没有携带轨道角动量的…...

机器人学的AGI实现路径:从专用智能到通用认知的跨越

文章目录 引言:机器人学的范式革命一、AGI与机器人学的融合现状1.1 传统机器人系统的局限1.2 AGI技术为机器人学带来的变革1.3 关键里程碑案例二、AGI机器人的核心技术栈2.1 多模态感知融合2.2 认知架构设计2.3 具身认知实现路径三、AGI机器人的实现路径3.1 阶段式发展路线3.2…...

香港中文大学 Adobe 推出 MotionCanvas:开启用户掌控的电影级图像视频创意之旅。

简介&#xff1a; 亮点直击 将电影镜头设计引入图像到视频的合成过程中。 推出了MotionCanvas&#xff0c;这是一种简化的视频合成系统&#xff0c;用于电影镜头设计&#xff0c;提供整体运动控制&#xff0c;以场景感知的方式联合操控相机和对象的运动。 设计了专门的运动条…...

基于STM32的学习环境控制系统设计

&#x1f91e;&#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是学习环境控制。 设备的详细功能见网盘中的文章《21、基于STM32的学习环境控制系统设计》&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1uWSZX2zbZwy9sY…...