多线程与并发编程 面试专题
多线程与并发编程 面试专题
- 线程的基础概念
- 基础概念
- 线程的创建
- 线程的状态
- 线程的终止方式
- start 与 run 区别
- 线程的常用方法
- 锁
- 锁的分类
- 深入synchronized
- 深入ReentrantLock
- 死锁问题
- 阻塞队列
- 线程池
线程的基础概念
基础概念
- 进程与线程
- 进程:指
运行中的程序
。 比如我们使用钉钉,浏览器,需要启动这个程序,操作系统会给这个程序分配一定的资源
(占用内存资源)。 - 线程:
CPU调度的基本单位
,每个线程执行的都是某一个进程的代码的某个片段。
- 进程:指
- 多线程
- 单个进程中同时运行多个线程。
- 多线程的不低是为了提高CPU的利用率。可以通过避免一些网络IO或者磁盘IO等需要等待的操作,让CPU去调度其他线程。这样可以大幅度的提升程序的效率,提高用户的体验。
- 多线程的局限
- 如果线程数量特别多,CPU在切换线程上下文时,会额外造成很大的消耗。
- 任务的拆分需要依赖业务场景,有一些异构化的任务,很难对任务拆分,还有很多业务并不是多线程处理更好。
- 线程安全问题:虽然多线程带来了一定的性能提升,但是再做一些操作时,多线程如果操作临界资源,可能会发生一些数据不一致的安全问题,甚至涉及到锁操作时,会造成死锁问题。
- 串行、并行、并发
- 串行:任务按严格顺序执行,前一个任务完成后再开始下一个。
- 并行:多个任务真正同时执行,依赖多核CPU或多处理器。
- 并发:任务交替执行(单核)或同时执行(多核),通过调度模拟“同时性”。
- 同步异步、阻塞非阻塞
- 同步:任务按顺序执行,前一个任务未完成时,后续任务必须等待。
- 异步:任务发起后,不等待其完成,继续执行后续操作,通过回调/通知获取结果。
- 阻塞:线程在等待某个操作(如I/O、锁)完成时,
暂停执行
,交出CPU控制权。 - 非阻塞:线程在等待操作完成时
继续执行其他任务
,通过轮询或回调检查结果。 - 同步阻塞:单线程调用阻塞式I/O(如传统文件读取)
- 同步非阻塞:循环调用非阻塞I/O,不断检查数据是否就绪。
- 异步阻塞:错误设计(如在异步操作中使用阻塞调用)。
- 异步非阻塞:异步I/O(如Node.js的fs.readFile),发起请求后继续执行其他任务,通过回调或Promise处理结果。
线程的创建
-
继承Thread类 重写run方法
public class MiTest {public static void main(String[] args) {MyJob t1 = new MyJob();t1.start();for (int i = 0; i < 100; i++) {System.out.println("main:" + i);}} } class MyJob extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("MyJob:" + i);}} }
-
实现Runnable接口 重写run方法
public class MiTest {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread t1 = new Thread(myRunnable);t1.start();for (int i = 0; i < 1000; i++) {System.out.println("main:" + i);}} } class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("MyRunnable:" + i);}} }
- 匿名内部类方式
Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("匿名内部类:" + i);}} });
- lambda方式
Thread t2 = new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println("lambda:" + i);} });
- 匿名内部类方式
-
实现Callable 重写call方法,配合FutureTask
Callable一般用于有返回结果的非阻塞的执行方法,同步非阻塞。public class MiTest {public static void main(String[] args) throws ExecutionException, InterruptedException {//1. 创建MyCallableMyCallable myCallable = new MyCallable();//2. 创建FutureTask,传入CallableFutureTask futureTask = new FutureTask(myCallable);//3. 创建Thread线程Thread t1 = new Thread(futureTask);//4. 启动线程t1.start();//5. 做一些操作//6. 要结果Object count = futureTask.get();System.out.println("总和为:" + count);} } class MyCallable implements Callable{@Overridepublic Object call() throws Exception {int count = 0;for (int i = 0; i < 100; i++) {count += i;}return count;} }
-
基于线程池构建线程
// 创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); while(true) {threadPool.execute(new Runnable() { // 提交多个线程任务,并执行@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " is running ..");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}}); }
线程的状态
- 新建状态(NEW):当程序使用
new 关键字创建了一个线程
之后,该线程就处于新建状态,此时仅由JVM 为其分配内存
,并初始化其成员变量的值
。 - 就绪状态(RUNNABLE):当线程
对象调用了 start()方法
之后,该线程处于就绪状态。 Java 虚拟机会为其创建方法调用栈和程序计数器
,等待调度运行。 - 运行状态(RUNNING):
就绪状态的线程获得了 CPU
,开始执行 run()方法的线程执行体
,则该线程处于运行状态。 - 阻塞状态(BLOCKED):线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。
阻塞的情况分三种:- 等待阻塞(o.wait->等待对列):运行(running)的线程执行 o.wait()方法, JVM 会把该线程放入等待队列(waitting queue)中。
- 同步阻塞(lock->锁池):运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
- 其他阻塞(sleep/join):运行(running)的线程执行Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、 join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
- 等待状态(WATING):
调用wait方法
就会处于WAITING状态,需要被手动唤醒。 - 时间等待状态(TIMED_WATING):
调用sleep方法或者join方法
,会被自动唤醒,无需手动唤醒。 - 结束状态(TERMINATED):线程会以下面三种方式结束,结束后就是死亡状态。
- run()或 call()方法执行完成,线程正常结束。异常结束。
- 线程抛出一个未捕获的 Exception 或 Error。调用 stop。
- 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
线程的终止方式
- 正常运行结束:程序运行结束,线程自动结束。
- 使用volatile修饰的共享变量(很少会用):通过修改共享变量在破坏死循环,让线程退出循环,结束run方法。
- Interrupt 方法结束线程:
- 线程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。 通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要
先捕获InterruptedException 异常之后通过 break 来跳出循环
,才能正常结束 run 方法。 - 线程未处于阻塞状态:使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。
- 线程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。 通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要
- stop 方法终止线程(线程不安全):强制让线程结束。
start 与 run 区别
- start() 方法来
启动线程
,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。 - 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
- 方法 run()称为
线程体
,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行
结束, 此线程终止。然后 CPU 再调度其它线程。
线程的常用方法
-
notify()
和notifyAll()
有什么区别- 唤醒的线程数量
- notify():随机唤醒
一个
正在该对象上调用wait()
的线程(无法指定具体唤醒哪个线程)。 - notifyAll():唤醒
所有
正在该对象上调用wait()
的线程。
- notify():随机唤醒
- 适用场景
- notify():当所有等待线程的
逻辑等价
(即任何一个线程被唤醒都能完成后续任务)时,使用 notify() 更高效。 - notifyAll():当等待线程的
条件不同
(需要确保所有等待线程都有机会重新检查条件)时,使用 notifyAll() 更安全。
- notify():当所有等待线程的
- 潜在风险
- notify() :如果多个线程等待的条件不同,可能唤醒错误的线程(例如生产者唤醒了另一个生产者),导致某些线程永远无法被唤醒(
线程饥饿
或死锁
)。 - notifyAll():唤醒所有线程会增加锁竞争,可能降低性能(但在现代 JVM 中影响较小)。
- notify() :如果多个线程等待的条件不同,可能唤醒错误的线程(例如生产者唤醒了另一个生产者),导致某些线程永远无法被唤醒(
- 唤醒的线程数量
-
sleep()
和wait()
有什么区别- 所属类与调用对象
- sleep()
- 属于
Thread
类的静态方法。 - 直接通过
Thread.sleep(ms)
调用,作用于当前线程。
- 属于
- wait()
- 属于
Object
类的实例方法。 - 必须在同步块(
synchronized
)中调用,作用于某个对象(如obj.wait()
)。
- 属于
- sleep()
- 锁的行为
- sleep()
- 不释放锁:线程休眠期间,仍然持有对象的锁(如果已获得)。
- 其他线程无法进入该对象的同步块。
- wait()
- 释放锁:调用后线程会释放对象的锁,允许其他线程获取锁并执行同步代码。
- 线程进入等待队列,直到被 notify()/notifyAll() 唤醒或超时。
- sleep()
- 使用场景
- sleep()
- 单纯让线程暂停执行一段时间,不涉及线程间协作。
- 示例:模拟耗时操作,定时任务间隔。
- wait()
- 用于线程间通信,需结合
notify()/notifyAll()
实现条件等待。 - 示例:生产者-消费者模型中,消费者等待队列有数据。
- 用于线程间通信,需结合
- sleep()
- 唤醒机制
- sleep():休眠结束后自动恢复,无需外部唤醒(除非被
interrupt()
中断)。 - wait():必须通过其他线程调用
notify()/notifyAll()
唤醒,或等待超时(若指定了超时时间)。
- sleep():休眠结束后自动恢复,无需外部唤醒(除非被
- 所属类与调用对象
-
异常处理
- 共同点:两者都可能抛出
InterruptedException
(当线程在等待/休眠期间被中断时)。 - 区别
wait()
必须在同步块中调用,否则抛IllegalMonitorStateException。sleep()
无此限制。
- 共同点:两者都可能抛出
-
线程状态
- sleep():线程进入
TIMED_WAITING
状态(若指定时间)或WAITING
(若时间无限,但实际sleep()
必须指定时间)。 - wait():线程进入
WAITING
状态(无超时)或TIMED_WAITING
状态(有超时)。
- sleep():线程进入
-
Java中
interrupted
和isInterrupted
方法的区别-
方法定义与作用对象
方法 类型 作用对象 描述 Thread.interrupted() 静态方法 当前执行线程 检查并清除当前线程的中断状态。 thread.isInterrupted() 实例方法 调用该方法的线程对象 仅检查线程的中断状态,不修改状态。
-
-
Thread类中的
yield
方法有什么作用- Thread.yield() 是一个用于线程调度的静态方法,其主要作用是 提示当前线程让出CPU资源,允许其他线程(尤其是优先级相同或更高的线程)有机会执行。
锁
锁的分类
- 悲观锁
- 核心思想:“
先加锁,再操作
”
默认认为并发操作一定会发生冲突,因此在操作数据前先加锁,确保独占访问。 - 实现方式
- 代码层面:
synchronized
关键字、ReentrantLock
等。 - 数据库层面:
SELECT ... FOR UPDATE
(行锁、表锁)、事务隔离级别(如串行化)。
- 代码层面:
- 特点
- 优点:保证强一致性,避免数据冲突。
- 缺点:加锁带来额外开销,可能引发线程阻塞、死锁,降低并发性能。
- 适用场景
- 写操作频繁,冲突概率高(如银行转账)。
- 需要严格保证数据一致性(如支付系统)。
- 核心思想:“
- 乐观锁
- 核心思想:“
先操作,再检查
”
默认认为冲突概率低,允许并发操作,但在提交时检查数据是否被修改,若冲突则重试或回滚。 - 实现方式
- 版本号机制:数据表增加
version
字段,更新时检查版本是否匹配(UPDATE ... SET version=version+1 WHERE id=1 AND version=old_version
)。 - CAS(Compare and Swap):Java中的
AtomicInteger
、AtomicReference
等原子类。
- 版本号机制:数据表增加
- 特点
- 优点:无锁操作,高并发场景下吞吐量高。
- 缺点:冲突频繁时重试成本高,可能引发ABA问题(需通过版本号或时间戳解决)。
- 适用场景:
- 读多写少,冲突概率低(如库存扣减、点赞计数)。
- 需要高并发性能(如缓存系统、计数器)。
- 核心思想:“
- 可重入锁
- 重入性:允许同一个线程多次获取同一把锁。
- 计数器机制:内部维护一个计数器,记录锁被同一个线程获取的次数。
- 每次
lock() 时
,计数器+1
。 - 每次
unlock()
时,计数器-1
。 - 计数器归零时,锁才被完全释放。
- 每次
- 避免自死锁:线程在递归调用或嵌套同步代码块中不会阻塞自己。
- 应用场景
- 递归函数中的同步代码。
- 对象方法之间嵌套调用(如
methodA()
调用methodB()
,二者都需要同步)。 - 需要灵活控制锁的获取和释放的场景(如 Java 的
ReentrantLock
)。
- 不可重入锁
- 不可重入性:同一线程重复获取锁时会被阻塞,导致死锁。
- 简单实现:通常没有记录持有者线程或重入次数的机制。
- 低开销:实现简单,但使用时需谨慎。
- 应用场景
- 简单同步场景(无嵌套或递归需求)。
- 需要最小化锁开销的场景(需自行确保不会重入)。
- 公平锁
- 顺序保证:严格按照线程请求锁的顺序分配锁(先到先得)。
- 避免饥饿:所有线程最终都能获取锁,不会无限等待。
- 性能开销:需要维护队列管理请求顺序,上下文切换频繁,吞吐量较低。
- 实现机制
- 通过队列(如 CLH 队列)记录等待线程,按顺序唤醒队首线程。
- Java 中可通过 ReentrantLock(true) 创建公平锁。
- 非公平锁
- 允许插队:新请求的线程可以直接尝试抢占锁,无需排队。
- 高吞吐量:减少线程切换开销,性能更高。
- 可能饥饿:某些线程可能长期无法获取锁(尤其在竞争激烈时)。
- 实现机制
- 线程直接尝试通过 CAS(Compare and Swap)抢占锁,失败后再进入队列等待。
- Java 中
ReentrantLock
默认是非公平锁。
- 互斥锁
- 独占访问:同一时间仅允许一个线程持有锁并访问资源。
- 写操作优先:适用于需要修改共享资源(如变量、文件、内存)的场景。
- 强一致性:确保临界区操作的原子性,避免数据竞争。
- 实现机制
- 原子操作:通过硬件支持的原子指令(如 CAS)实现锁的获取与释放。
- 阻塞等待:未获取锁的线程进入阻塞状态,直到锁被释放后被唤醒。
- 典型应用场景
- 修改全局变量或共享数据结构。
- 文件写入、数据库更新等写操作。
- 需要严格保证操作原子性的场景(如转账)。
- 共享锁
- 并发读取:允许多个线程同时持有锁并读取资源。
- 写互斥:若存在写操作,则所有读/写线程必须等待。
- 读写分离:通常与互斥锁结合使用,形成 读写锁(
Read-Write Lock
)。
- 实现机制
- 计数器管理:记录当前持有锁的读线程数量。
- 写锁优先:写锁请求会阻塞后续读锁,确保写操作不被饥饿。
- 典型应用场景
- 读多写少的场景(如缓存、配置信息读取)。
- 数据库查询优化(共享锁允许多个事务并发读同一数据)。
- 文件读取(多个线程可同时读取文件内容)。
深入synchronized
深入ReentrantLock
死锁问题
- 死锁产生的必要条件
- 互斥(Mutual Exclusion):资源一次只能被一个线程占用。
- 请求与保持(Hold and Wait):线程持有至少一个资源,同时请求其他线程持有的资源。
- 不可剥夺(No Preemption):资源只能由持有它的线程主动释放,不能被强制剥夺。
- 循环等待(Circular Wait):多个线程形成环形等待链,每个线程都在等待下一个线程释放资源。
- 常见死锁场景
- 场景1:嵌套锁顺序不一致
// 线程1 synchronized (lockA) {synchronized (lockB) { ... } }// 线程2 synchronized (lockB) {synchronized (lockA) { ... } // 可能导致死锁 }
- 场景2:生产者-消费者模型中的资源竞争
多个线程在共享队列中同时获取插入和删除的锁。 - 场景3:数据库事务中的行级锁
多个事务以不同顺序更新相同的数据行。
- 场景1:嵌套锁顺序不一致
- 如何检测死锁
- JVM工具
- 使用
jstack <pid>
导出线程栈,查找Found one Java-level deadlock
。 - 使用
jconsole
或VisualVM
查看线程状态。
- 使用
- 代码检测
ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] threadIds = bean.findDeadlockedThreads(); if (threadIds != null) {System.out.println("检测到死锁!"); }
- JVM工具
- 死锁预防与解决
- 方法1:统一加锁顺序
- 核心思想:确保所有线程按相同的顺序请求资源。
- 示例:在转账场景中,按账户哈希值排序锁。
void transfer(Account from, Account to, int amount) {Account first = from.hashCode() < to.hashCode() ? from : to;Account second = from.hashCode() < to.hashCode() ? to : from;synchronized (first) {synchronized (second) {// 转账逻辑}} }
- 方法2:避免嵌套锁
- 尽量减少锁的作用域,使用无锁设计(如CAS操作)或线程安全容器(如
ConcurrentHashMap
)。
- 尽量减少锁的作用域,使用无锁设计(如CAS操作)或线程安全容器(如
- 方法3:设置超时等待
- 使用
tryLock
替代synchronized
,设定超时时间。Lock lockA = new ReentrantLock(); Lock lockB = new ReentrantLock(); if (lockA.tryLock(1, TimeUnit.SECONDS)) {try {if (lockB.tryLock(1, TimeUnit.SECONDS)) {try { ... } finally { lockB.unlock(); }}} finally { lockA.unlock(); } }
- 使用
- 方法4:破坏不可剥夺条件
- 允许系统强制回收资源(需谨慎,可能导致数据不一致)。
- 方法5:使用线程池隔离
- 将不同任务分配到独立的线程池,避免资源竞争。
- 方法1:统一加锁顺序
阻塞队列
- 阻塞队列原理
- 当
队列中没有数据
的情况下,消费者端
的所有线程都会被自动阻塞(挂起)
,直到有数据放入队列。 - 当
队列中填满数据
的情况下,生产者端
的所有线程都会被自动阻塞(挂起)
,直到队列中有空的位置,线程被自动唤醒。
- 当
- 阻塞队列的主要方法
- 阻塞队列分类
- ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列。
- LinkedTransferQueue:由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
线程池
-
线程池分类
- newCachedThreadPool:
创建一个可根据需要创建新线程的线程池
,但是在以前构造的线程可用时将重用它们。
- newFixedThreadPool:
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线。
- newScheduledThreadPool:
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
- newSingleThreadExecutor:Executors.newSingleThreadExecutor()
返回一个线程池
(这个线程池只有一个线程) ,这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。
- newCachedThreadPool:
-
线程池配置属性
- 核心线程数(corePoolSize)
- 作用:线程池中始终保持的最小线程数,即使这些线程处于空闲状态。
- 注意事项:
- 默认情况下,核心线程不会因空闲而被回收(除非配置了
allowCoreThreadTimeOut
)。 - 适用于处理常规任务负载。
- 默认情况下,核心线程不会因空闲而被回收(除非配置了
- 最大线程数(maximumPoolSize)
- 作用:线程池允许创建的最大线程数量。当工作队列已满且当前线程数小于最大线程数时,会创建新线程处理任务。
- 注意事项:
- 需根据系统资源(如CPU核数、内存)和任务类型(CPU密集型或IO密集型)合理设置。
- 若设置为与corePoolSize相同,则线程池退化为固定大小线程池。
- 空闲线程存活时间(keepAliveTime)
- 作用:非核心线程(超出corePoolSize的线程)在空闲状态下的存活时间。超过该时间后,线程会被终止回收。
- 单位:通常与TimeUnit配合使用(如秒、毫秒)。
- 注意事项:
- 若允许核心线程超时(通过
allowCoreThreadTimeOut(true)
),核心线程也会受此参数影响。
- 若允许核心线程超时(通过
- 工作队列(workQueue)
- 作用:用于保存等待执行任务的阻塞队列。
常见类型:- 无界队列(如
LinkedBlockingQueue
):可能导致内存溢出。 - 有界队列(如
ArrayBlockingQueue
):需合理设置容量。 - 同步移交队列(如
SynchronousQueue
):不存储任务,直接将任务交给线程。
- 无界队列(如
- 选择策略:
- 任务量波动大时,优先使用有界队列避免资源耗尽。
- 高吞吐场景可使用
LinkedBlockingQueue
,快速响应场景可使用SynchronousQueue
。
- 作用:用于保存等待执行任务的阻塞队列。
- 线程工厂(threadFactory)
- 作用:自定义线程创建方式,可设置线程名称、优先级、是否为守护线程等。
- 示例:
ThreadFactory factory = r -> {Thread t = new Thread(r);t.setName("my-pool-" + t.getId());return t; };
- 拒绝策略(RejectedExecutionHandler)
- 作用:当线程池无法接受新任务(队列已满且线程数达上限)时的处理策略。
常见策略:- AbortPolicy(默认):抛出
RejectedExecutionException
。 - CallerRunsPolicy:由提交任务的线程直接执行任务。
- DiscardPolicy:静默丢弃被拒绝的任务。
- DiscardOldestPolicy:丢弃队列中最旧的任务,并重新提交被拒绝的任务。
- AbortPolicy(默认):抛出
- 自定义策略:可扩展RejectedExecutionHandler接口实现特定逻辑(如记录日志或重试)。
- 作用:当线程池无法接受新任务(队列已满且线程数达上限)时的处理策略。
- 其他配置
- allowCoreThreadTimeOut:允许核心线程因空闲超时被回收(需
keepAliveTime > 0
)。 - 预热核心线程:通过
prestartAllCoreThreads()
提前启动所有核心线程。
- allowCoreThreadTimeOut:允许核心线程因空闲超时被回收(需
- 核心线程数(corePoolSize)
相关文章:
多线程与并发编程 面试专题
多线程与并发编程 面试专题 线程的基础概念基础概念线程的创建线程的状态线程的终止方式start 与 run 区别线程的常用方法 锁锁的分类深入synchronized深入ReentrantLock死锁问题 阻塞队列线程池 线程的基础概念 基础概念 进程与线程 进程:指运行中的程序。 比如我…...
大语言模型-1.2-大模型技术基础
简介 本博客内容是《大语言模型》一书的读书笔记,该书是中国人民大学高瓴人工智能学院赵鑫教授团队出品,覆盖大语言模型训练与使用的全流程,从预训练到微调与对齐,从使用技术到评测应用,帮助学员全面掌握大语言模型的…...
【C++】每日一练(轮转数组)
本篇博客给大家带来的是用C语言来解答轮转数组! 🐟🐟文章专栏:每日一练 🚀🚀若有问题评论区下讨论,我会及时回答 ❤❤欢迎大家点赞、收藏、分享! 今日思想:不服输的少年啊…...
dify本地源码win10部署
我的win10版本还比较老,winR,输入winver 只要高于我这个版本的都没啥大问题吧,我的安装docker Desktop,搞死人了, 就是win10的Win10 22H2 64之前的版本 win10低版本安装,里面包含wdl2安装程序 https://…...
Spring Cloud Config - 动态配置管理与高可用治理
引言:为什么需要配置中心? 在微服务架构中,配置管理面临分散化、多环境、动态更新三大挑战。传统基于application.yml等配置文件的硬编码方式,导致以下问题: • 环境差异:开发、测试、生产环境配置混杂&a…...
大模型最新面试题系列:微调篇之微调框架(一)
一. 在DeepSpeed中配置零冗余优化(ZeRO)实现显存优化的步骤 核心原理 ZeRO通过分片(Sharding)技术将模型参数、梯度和优化器状态分布到多卡,消除冗余存储。三个阶段逐步减少显存占用: Stage 1࿱…...
windows第十三章 GDI绘图技术
文章目录 GDI绘图函数介绍设备上下文函数m_hDC GDI对象画笔画刷位图字体区域 案例分享 GDI绘图函数介绍 绘图函数在CDC类里 设备上下文 DC:device context 设备上下文,显卡驱动程序,控制硬件,每个厂商的都不同,操作系统层面&am…...
使用 Nginx 进行前端灰度发布的策略与实践
1. 引言 灰度发布的概念 灰度发布,也称为金丝雀发布,是一种软件发布策略,通过向一小部分用户群体逐步推出新版本,收集反馈并监控性能,以确保新版本在大规模部署前不会出现问题。这种方法可以有效降低发布风险&#x…...
有了大语言模型还需要 RAG 做什么
一、百炼平台简介 阿里云的百炼平台就像是一个超级智能的大厨房,专门为那些想要做出美味AI大餐的企业和个人厨师准备的。你不需要从头开始做每一道菜,因为这个厨房已经为你准备了很多预制食材(预训练模型),你可以根据…...
pytest快速入门 - 目录:半天掌握pytest
1 pytest快速入门 - 目录 本系列文章将快速的带领用户进入pytest领域,通过阅读本专栏,用户将可以熟练掌握pytest的基本用法,同时对测试前置条件的构造、后置条件的清理等有较深入的了解,特别是后置条件的执行完备度有一个认识。 …...
2.4 python网络编程
在当今数字化的时代,网络连接着世界的每一个角落。从简单的网页浏览到复杂的分布式系统,网络编程无处不在。Python 作为一种功能强大且易于学习的编程语言,在网络编程领域占据着重要的地位。它丰富的库和简洁的语法使得开发者能够高效地构建各…...
网络变压器的主要电性参数与测试方法(4)
Hqst盈盛(华强盛)电子导读:网络变压器的主要电性参数与测试方法(4).. 今天我们继续来看看网络变压器的2个重要电性参数与它的测试方法: 1.反射损耗(Return loss&…...
【Springboot知识】开发属于自己的中间件健康监测HealthIndicate
文章目录 **一、技术栈****二、项目结构****三、依赖配置 (pom.xml)****四、配置文件 (application.yml)****五、自定义健康检查实现****1. Redis健康检查****2. Elasticsearch健康检查****3. Kafka健康检查****4. MySQL健康检查** **六、自定义健康检查接口 (可选)****七、测试…...
蓝桥杯备赛-二分-技能升级
问题描述 小蓝最近正在玩一款 RPG 游戏。他的角色一共有 NN 个可以加攻击力的技能。 其中第 ii 个技能首次升级可以提升 AiAi 点攻击力, 以后每次升级增加的点数 都会减少 Bi。「AiBi⌉Bi。「BiAi⌉ (上取整) 次之后, 再升级该技能将不会改变攻击力。 现在小蓝可以…...
【GPT入门】第18课 langchain介绍与API初步体验
【GPT入门】langchain第一课 langchain介绍与API初步体验 1. langchain介绍定义特点1. 模块化与灵活性2. 链式调用机制3. 数据连接能力4. 记忆管理功能5. 提示工程支持6. 可扩展性 2.langchain核心组件架构图3. 最简单的helloworld入门 1. langchain介绍 LangChain 是一个用于…...
Django部署Filemanagement
Pycharm搭建项目安装虚拟环境 mysqlclient对mysql的安装,配置有要求 pymsql伪装成mysqlclient,pymysql可以操纵mysql pip install pymysql操作sql5.7 mysql8.0会出现与pycharm不兼容问题,会报错,所以降到5.7 # 进入mysql 需要…...
Python的types库学习记录
types 库是 Python 标准库中的一部分,它提供了与 Python 对象类型相关的工具和常量,有助于在运行时处理和操作不同类型的对象。 以下是对 types 库的详细介绍: 主要用途 • 类型检查:在运行时判断对象的类型。 • 动态创建和操作…...
C# 表达式树详解
总目录 前言 在 C# 中,表达式树(Expression Trees)是一种强大的特性,允许开发者将代码表示为数据结构。这使得我们可以在运行时动态地创建和操作代码逻辑,广泛应用于 LINQ 查询、动态方法生成以及反射等领域。本文将深…...
分别用树型和UML结构展示java集合框架常见接口和类
树型结构展示java集合框架常见接口和类 Java 集合框架中的接口和子类关系可以用树形结构来展示。以下是一个简化的树形结构,展示了主要的接口和一些重要的实现类: java.util.Collection ├── java.util.List │ ├── java.util.ArrayList │ ├…...
蓝桥杯备赛-二分-青蛙过河
问题描述 小青蛙住在一条河边, 它想到河对岸的学校去学习。小青蛙打算经过河里 的石头跳到对岸。 河里的石头排成了一条直线, 小青蛙每次跳跃必须落在一块石头或者岸上。 不过, 每块石头有一个高度, 每次小青蛙从一块石头起跳, 这块石头的高度就 会下降 1 , 当石头的高度下降…...
uniapp+微信小程序+最简单局部下拉刷新实现
直接上代码 <scroll-view style"height: 27vh;" :scroll-top"scrollTop" scroll-y"true"scrolltolower"onScrollToLower1" lower-threshold"50"refresher-enabled"true" refresherrefresh"onRefresherR…...
Spring Boot 3.x 中 @NotNull 与 @NonNull 的深度解析
在 Java 开发领域,尤其是在 Spring Boot 生态系统中,空指针异常(NPEs)始终是一个顽固的挑战。这些运行时错误可能导致应用程序崩溃、数据不一致以及糟糕的用户体验。为了应对这一问题,Java 社区开发了各种空安全机制&a…...
SQLark 实战 | 如何从Excel、csv、txt等外部文件进行数据导入
数据导入导出是应用开发者在平时开发中最常用的操作之一,SQLark 里提供了方便的图形化界面来完成导入导出。本文先和大家分享如何从 Excel、csv、txt 等外部文件导入数据到数据库表中。 👉 前往 SQLark 官网:www.sqlark.com 下载全功能免费版…...
MATLAB中envelope函数使用
目录 说明 示例 chirp 的解析包络 使用滤波器计算多通道信号的解析包络 录音信号的移动 RMS 包络 语音信号的峰值包络 不对称序列的包络 envelope函数的功能是提取信号的包络。 语法 [yupper,ylower] envelope(x) [yupper,ylower] envelope(x,fl,analytic) [yupper,…...
ES搭建详细指南+常见错误解决方法
Elasticsearch(ES)是一款开源的、分布式的、RESTful风格的搜索和数据分析引擎。它用于全文搜索、结构化搜索、分析等场景。以下是Elasticsearch的搭建步骤以及处理常见错误的方法。 Elasticsearch搭建步骤: 1.环境准备: 确保你的…...
Unity 封装一个依赖于MonoBehaviour的计时器(上) 基本功能
灵感来自下面这本书的协程部分,因此我就自己尝试写了一个 我的新书Unity3D游戏开发(第3版) | 雨松MOMO程序研究院 如果你不知道什么是协程:unity保姆级教程之协同程序_unity协同-CSDN博客 一句话概括:协程就是单线程的异步操作,其作用于Unity的主线程 1…...
PostgreSQL数据库版本支持策略
PostgreSQL数据库版本支持策略 主要版本会进行复杂的更改,因此无法以向后兼容的方式维护数据目录的内容。重大升级需要转储/重新加载数据库或使用pg_upgrade应用程序。我们还建议您阅读您计划升级到的主要版本的升级部分。您可以从一个主要版本升级到另一个…...
应用层之网络应用模型,HTTP/HTTPS协议
应用层是网络协议栈的最顶层,直接为应用程序提供通信服务,定义了不同主机间应用进程交互的规则,包括报文类型、语法、语义及通信时序 一、网络应用模型 1.定义及特点 模型定义核心特点典型应用场景C/S客户端向服务器发起请求,服…...
(七)Spring Boot学习——Redis使用
有部分内容是常用的,为了避免每次都查询数据库,将部分数据存入Redis。 一、 下载并安装 Redis Windows 版的 Redis 官方已不再维护,你可以使用 微软提供的 Redis for Windows 版本 或者 使用 WSL(Windows Subsystem for Linux&a…...
11 | 给 Gin 服务器添加中间件
提示: 所有体系课见专栏:Go 项目开发极速入门实战课;欢迎加入 云原生 AI 实战 星球,12 高质量体系课、20 高质量实战项目助你在 AI 时代建立技术竞争力(聚焦于 Go、云原生、AI Infra);本节课最终…...
selenium等待
通常代码执行的速度⽐页⾯渲染的速度要快,如果避免因为渲染过慢出现的⾃动化误报的问题呢?可以使⽤selenium中提供的三种等待⽅法: 1. 隐式等待(Implicit Wait) 隐式等待适用于全局,它告诉 WebDriver 在查找元素时等待一定的时间,直到元素出现。 如果超时,WebDriver 不…...
为什么List、Set集合无法在遍历的时候修改内部元素
以常用集合ArrayList为例,ArrayList 在遍历过程中无法直接修改内部元素的结构(例如通过 remove() 或 add() 方法修改元素),是因为 遍历的过程中修改结构 可能会导致 不一致的行为、并发修改异常 或 逻辑错误。 注意:和…...
使用 Elasticsearch 构建多模式 RAG 系统:哥谭市的故事
作者:来自 Elastic Alex Salgado 学习如何构建一个多模态检索增强生成 (RAG) 系统,该系统集成文本、音频、视频和图像数据,以提供更丰富的、具有上下文的信息检索。 在这篇博客中,你将学习如何使用 Elasticsearch 构建一个多模态 …...
单一责任原则在Java设计模式中的深度解析
在软件开发中,设计模式提供了一种解决特定问题的思路。在众多的设计原则中,单一责任原则(Single Responsibility Principle,SRP)是一个非常重要的概念。它主要强调一个类应该只有一个责任,也就是说…...
设计模式学习记录
设计模式23种 创建型抽象工厂模式工厂模式生成器模式原型模式单例模式 结构型适配器模式桥接模式组合模式装饰模式外观模式享元模式代理模式 行为型责任链模式命令模式解释器模式迭代器模式中介者模式备忘录模式观察者模式状态模式策略模式模版方法模式访问者模式 创建型 与对…...
set_clock_groups
一、命令参数与工具处理逻辑 核心参数定义 参数定义工具行为工具兼容性-asynchronous完全异步时钟组,无任何相位或频率关系(如独立晶振、不同时钟树)工具完全禁用组间路径的时序分析,但需用户自行处理跨时钟域(CDC&a…...
QT创建项目(项目模板、构建系统、选择类、构建套件)
1. 项目模版 项目类型界面技术适用场景核心依赖模块开发语言Qt Widget ApplicationC Widgets传统桌面应用(复杂控件)Qt WidgetsCQt Console Application无 GUI命令行工具、服务Qt CoreCQt Quick ApplicationQML/Quick现代跨平台应用(动画/触…...
麒麟系统利用pycharm生成deb文件
在麒麟系统(Kylin OS)上使用 PyCharm 进行 Python 开发并生成 .deb 可安装软件包,可以按照以下步骤进行操作: 1. 准备工作 安装 PyCharm:确保已经在麒麟系统上安装了 PyCharm,可以使用官方提供的安装包进…...
超声重建,3D重建 超声三维重建,三维可视化平台 UR 3D Reconstruction
1. 超声波3D重建技术的实现方法与算法 技术概述 3D超声重建是一种基于2D超声图像生成3D体积数据的技术,广泛应用于医学影像领域。通过重建和可视化三维结构,3D超声能够显著提高诊断精度和效率,同时减少医生的脑力负担。本技术文档将详细阐述…...
Qt 信号与槽
目录 Qt信号和槽 connect函数 connect使用方法 自定义信号 与 自定义槽 Qt界面化工具自动生成的槽 自定义信号 带参数的信号和槽 信号与槽的断开 Qt信号和槽 谈到信号,设计3个要素 信号源:谁发出了信号 信号触发条件:哪个控件的哪个…...
卷积神经网络 - 卷积的变种、数学性质
本文我们来学习卷积的变种和相关的数学性质,为后面学习卷积神经网络做准备,有些概念可能不好理解,可以先了解其概念,然后慢慢理解、逐步深入。 在卷积的标准定义基础上,还可以引入卷积核的滑动步长和零填充来增加卷积…...
ubuntu 和 RV1126 交叉编译Mosqutiio-1.6.9
最近需要交叉编译mosquitto,遇到一些小问题记录一下。 1.众所周知使用它自带的Makefile编译的时候,只需要在编译前,指定它config.mk中的变量:CFLAGS头文件路径 和 LDFLAGS库文件路径就ok,例子如下: expor…...
从零开始学习机器人---如何高效学习机械原理
如何高效学习机械原理 1. 理解课程的核心概念2. 结合图形和模型学习3. 掌握公式和计算方法4. 理论与实践相结合5. 总结和复习6. 保持好奇心和探索精神 总结 机械原理是一门理论性和实践性都很强的课程,涉及到机械系统的运动、动力传递、机构设计等内容。快速学习机械…...
STM32 RS232通信开发全解析 | 零基础入门STM32第五十九步
主题内容教学目的/扩展视频RS232串口电路原理,跳线设置,驱动程序。与超级终端通信。了解电路原理和RS232协议。 师从洋桃电子,杜洋老师 📑文章目录 一、RS232通信系统架构二、RS232核心原理与硬件设计2.1 电气特性对比2.2 典型电路…...
文献分享: 对ColBERT段落多向量的剪枝——基于学习的方法
原论文 1. 导论 & \textbf{\&} &方法 1️⃣要干啥:在 ColBERT \text{ColBERT} ColBERT方法中,限制每个段落要保留的 Token \text{Token} Token的数量,或者说对段落 Token \text{Token} Token进行剪枝 2️⃣怎么干:注…...
(已解决)aws 上 部署Splunk 负载均衡unhealthy
在AWS 部署Splunk 服务,instance 是后端的EC2, 我把splunk 服务起好后,发现port : 8000 是listening: #netstat -an | grep 80 tcp 0 0 127.0.0.1:8065 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:8089 0.0.0.0:* …...
C# 异步编程
概述 同步:指必须等待前一个操作完成,后续操作才能继续。同步操作会阻塞线程直到任务完成。 异步:异步操作不会阻塞线程,允许程序在等待某个任务完成的同时,继续执行其他任务。 异步编程适用场景: 1、从…...
缓存之美:Guava Cache 相比于 Caffeine 差在哪里?
大家好,我是 方圆。本文将结合 Guava Cache 的源码来分析它的实现原理,并阐述它相比于 Caffeine Cache 在性能上的劣势。为了让大家对 Guava Cache 理解起来更容易,我们还是在开篇介绍它的原理: Guava Cache 通过分段(…...
Go string 字符串底层逻辑
在 Go 语言中,string 类型的底层结构是一个结构体,包含两个字段:一个指向字节数组的指针和该字节数组的长度。以下是其在 Go 源码中的大致定义:type stringStruct struct {str unsafe.Pointerlen int } str:这是一个指…...
高效集成聚水潭采购退货数据到MySQL的最佳实践
聚水潭数据集成到MySQL:采购退货单的高效对接方案 在企业的数据管理和分析过程中,数据的准确性和实时性至关重要。本文将分享一个具体的系统对接集成案例:如何通过轻易云数据集成平台,将聚水潭中的采购退货单数据高效地集成到MyS…...