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

JavaEE--2.多线程

1.认识线程(Thread)

1.1概念

        1)什么是线程

        ⼀个线程就是⼀个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码.

        2)为什么要有线程

        首先, "并发编程" 成为 "刚需".

                1.   单核 CPU 的发展遇到了瓶颈. 要想提⾼算⼒, 就需要多核 CPU. ⽽并发编程能更充分利⽤多核 CPU 资源.

                2. 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程

        其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量

         创建线程比创建进程更快.

         销毁线程比销毁进程更快.

         调度线程比调度进程更快.

        最后, 线程虽然比进程轻量, 但是⼈们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程" (Coroutine)

多进程的问题就是太"重"

        创建/销毁进程的开销比较大(时间,空间)

        一旦需求常见,需要频繁地创建销毁进程,开销非常明显.

        最典型的例子就是服务器的开发:针对每个发送请求的客户端都创建一个单独的进程,由这个进程负责给客户端进行服务

为了解决进程开销比较大的问题,发明了"线程"(Thread)

                               线程可以理解成更轻量的进程,也能实现并发编程,创建/销毁的开销比进程更低.

因此,多线程的编程就成了当下主流的并发编程方式.

 

        所谓的进程,在系统中是以PCB(进程控制块)这样的结构体来描述的,通过链表的形式来组织的

        对于系统中,线程同样也是通过PCB来描述的(Linux)

        一个进程其实是一组PCB

        一个线程是一个PCB

        所以,其中存在"包含关系”,一个进程中,可以包含多个线程

        此时每个线程都可以独立的到CPU上调度执行

        线程是系统“调度执行”的基本单位

        进程是系统“资源分配”的基本单位

        解释:一个可执行程序,运行的时候(双击),操作系统就会创建进程,给这个程序分配各种系统资源(CPU,内存,硬盘,网络带宽......),同时,也会在这个进程中,创建出一个或者多个线程,这些线程再去CPU上调度执行.

        上个文章提到的"进程调度"相当于"线程调度",针对"只包含一个线程的进程来说"

        如果有多个线程在一个进程中,每个线程,都会有自己的状态,优先级,上下文,记账信息,每个都会各自独立的在CPU上调度执行

        进程要么包含一个线程,要么包含多个线程,不能没有线程

        同一个进程的这些线程,共用着同一个系统资源

        线程比进程更轻量,主要就在与创建线程省去了"分配资源"过程,销毁线程也省去了"释放资源"过程.

分配资源这个操作的开销大

        一旦创建进程,同时也会创建第一个线程=>就会负责分配资源

        一旦后续创建第二个第三个线程,就不必再重新分配资源了....

        从微观角度看,多个核心,每个核心都可以执行一个线程,这些核心之间的执行过程是"同时执行的",这时称之为"并行"

        一个核心,也可以按照"分时复用",来切换多个线程,从微观上来看.多个线程是"一个接一个"的执行的,由于调度速度足够快,宏观上看起来好像"同时执行",这时称之为"并发"

        提升效率的关键是在于充分利用多核心进行"并行执行"

        如果只是"微观并发",速度是没有提升的

        真正能提升速度的是"并行"

线程有关的问题:

1.线程数目太多

        如果线程数目太多,比如超出了CPU核心数目.此时就无法在微观上完成"并行"执行,势必会存在严重的"竞争".

2.进程资源"冲突"

        当线程数目多了之后,此时就容易发生"冲突",由于多个线程,使用的是同一份资源(内存资源)

        内存资源指的是代码中定义的变量/对象,如果多个线程同时对一个变量进行读写操作(尤其是写操作)容易发生冲突

        一旦发生冲突,就可能使程序出现问题("线程安全问题"[重点内容,难点内容])

3.线程异常

        当一个进程中有多个线程的时候,一旦某个线程抛出异常,这个时候,如果未能妥善处理,可能导致整个进程崩溃,其他线程就会随之崩溃

总结下(这也是高频的面试题)

进程线程的概念和区别

        1.进程包含线程

一个进程里可以有一个线程,也可以有多个线程,不能没有线程

        2.进程是系统资源分配的基本单位

线程是系统调度执行的基本单位

        3.同一个进程里的线程之间,共用着同一份系统资源(内存,硬盘,网络带宽等...)

尤其是"内存资源",就是代码中定义的变量/对象...

                                        编程中,多个线程,是可以共用同一份变量的

        4.线程是当下实现并发编程的主流方式,通过多线程,就可以充分利用好多核CPU

        但是不是线程数目越多越好,线程数目达到一定程度,把多个核心都充分利用了之后,此时继续增加线程无法再提高效率,甚至可能会影响效率(线程调度也是有开销的)

        5.多个线程之间,可能会互相影响,线程安全问题,一个线程抛出异常,也可能会把其他线程也一起带走

        6.多个进程之间,一般不会相互影响,一个进程崩溃了,不会影响到其他进程(这一点也称为"进程的隔离性")

3)进程和线程的区别

        进程是包含线程的,每个进程至少有一个线程,即主线程.

        进程和进程之间不共享内存空间,同一进程的线程之间共享同一个内存空间

        进程是资源分配的基本单位,线程是系统调度的基本单位

        一个进程挂了一般不会影响到其他进程,一个线程挂了可能会把同进程的其他线程一起带走(整个进程崩溃).

4)Java的线程和操作系统线程的关系

        线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户提供了一些API供用户使用(例如Linux的pthread库).

        Java标准库中的Thread类可以视为对操作系统提供的API进行了进一步的抽象和封装.

        Java(JVM)把这些系统API封装好了,咱们不需要关注系统原生API,只需要了解好Java提供的这一套API就行了


 

1.2第一个多线程程序

Thread标准库:

        这个类就负责完成多线程相关的开发.

此处Thread类可以直接使用,不需要导入任何的包

(因为有的类比较特殊,默认已经导入了,比如String)

此处的继承不是主要目的,更主要的目的是为了重写run方法

        调用start就会在进程内部创建出新的线程,新的线程就会执行刚才run里面的代码

  上述代码没有run方法没有手动的去调用但是最终也是执行了像run这种用户手动定义,但是没有用户手动调用,最终这个方法被系统/库/框架进行调用了此时,这样的方法被称为"回调函数"(callback)回调函数是编程中一个非常重要的概念

回调函数是一个非常重要的概念!!

回调函数概念其实目前至少遇到过两次

1.C语言中的函数指针

        函数指针主要有两个用途

        a)作为回调函数

        b)实现转移表--降低代码的复杂程度

2.Java数据结构中的优先级队列

        优先级队列必须先定义好对象的"比较规则"     Comparable   Compareto

                                                                                Comparator     Compare        

                自己定义了这些比较规则,但是并没有调用,此时都是标准库本身内部的逻辑负责调用

       

        这个代码运行起来,是一个进程,但是这个进程实际上是包含了两个线程!

        调用main方法的线程称为"主线程"

        一个进程至少得包含一个线程,至少这一个线程就是主线程

        t.start() 这里又手动的创建了新的线程

         主线程和新线程都并发/并行的在CPU上执行

        Thread,sleep();是Thread的静态方法   类名.方法名

        Java标准库提供的方法

        C语言中也有类似的,Sleep()    (Windows的系统函数)

        多线程之间谁先去CPU上调度执行,这个过程是"不确定的"(不是数学意义上的随机)

        而是这个调度顺序取决于操作系统,内核里"调度器"的实现

        调度器里有一套规则,咱们作为应用程序开发没办法进行干预,也感受不到

        只能将这个过程近似的视为"随机"                                          "抢占式执行"

借助第三方工具更直观的来看这两个线程的情况

在jdk的bin目录中打开jconsole

可以看出当前java进程中线程的情况,左下角可以看出一个java进程中不只有两个线程,还有别的

main是主线程,调用main方法的

Thread-0是代码中自己创建的线程,它的命名规律是Thread-数字


在IDEA的调试器,也能看到类似的信息

        

就可以手动的切换都某个线程上,看你需要关注的一些信息

创建线程的写法

1)继承Thread,重写run

上面的写法就是继承Thread类

1.继承 Thread 来创建⼀个线程类.

class MyThread extends Thread {@Overridepublic void run() {System.out.println("这⾥是线程运⾏的代码");}
}

2.创建 MyThread 类的实例

MyThread t = new MyThread();

3.调用 start 方法启动线程

t.start();

全部代码:

2)实现Runnable接口,重写run

1.实现 Runnable 接口

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这⾥是线程运⾏的代码");}
}

2.创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为 target 参数.

Thread t = new Thread(new MyRunnable());

3.调用 start 方法

t.start();

全部代码:

        Runnable就是用来描述"要执行的任务"  是什么

        有人认为Runnable这种做法更有利于"解耦合",这只是一个任务,并不是和"线程"这样的概念强相关

        后续执行这个任务的载体可以是线程也可以是别的

        别的在这里比如后续会介绍的线程池来执行任务

        再比如可以通过"虚拟线程"(协程)来执行~~

        线程是轻量级进程,因为进程太重量了,随着对于性能要求进一步提高,开始嫌弃线程也太重量了

        于是引入了"协程".(轻量级线程,纤程)

        这个协程的概念在后续的java版本中就被引入了,起的名叫做"虚拟线程"

3)内部匿名类

内部匿名类没有类目

        Runnable子类对象

Thread子类对象

1.定义匿名内部类,这个类是Thread的子类

2.类的内部重写的父类run方法

3.创建了一个子类的实例,并且把实例的引用赋值给了t

匿名内部类就是一次性使用的类,用完就丢

4.lambda表达式创建Runnable子类

 Thread thread=new Thread(() -> {while(true){System.out.println("这是线程输出的");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();while(true){System.out.println("这是main运行的代码");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}

        这也是推荐的写法

        后续多以lambda表达式来创建多线程

2.1Thread的常见构造方法

方法说明
Thread()创建线程对象
Tread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象,并命名
Tread(Runnable target,String name)使用Runnable对象创建线程对象,并命名
[了解]Thread(ThreadGoup goup,Runnable target)线程可以被用来分组管理,方便统一的设置线程的一些属性

注:给线程起名字不会影响到线程的执行效果,但是有一个合适的名字有利于调试的结果

不起名字默认就是Thread-0,Thread-1...

        现在很少会使用线程组,线程的相关属性用的也不多,现在更多的是会使用线程池

2.2Thread的几个常见的属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

1.ID是线程的唯一标识,不同的线程不会重复

        JVM自动分配的,Thread对象的身份标识

2.名称是各种调试工具用到的

3.状态表示线程当前所处的一个情况,下面我们会具体说明

        就绪/阻塞状态                     就绪状态的名字为RUNNABLE

        通常情况下,一个Thread对象,就是对应到系统内部的一个线程(PCB),但是也可能会存在一个情况,Thread对象存在,但是系统内部的线程已经没了/还没有创建

         

4.优先级高的线程理论上来说更容易被调度到

        设计不同的优先级影响系统的调度,这里的影响是基于"统计"规则的影响,直接肉眼观察河南观察到效果

5.关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后才会结束运行

        后台线程:如果这个线程执行的过程中不能阻止进程结束(虽然线程在执行,但是进程结束了,这个线程也会被随之带走),这样的线程就是"后台线程"

        前台线程:如果某个线程在执行的过程中,能够阻止进程的结束,这个线程就是"前台进程 "

6.是否存活,即简单的理解run的方法是否结束了

        true为存在,false为无了

由于主线程和thread线程并发执行,主线程执行上述4个操作,执行了一部分的时候thread线程就执行打印了

RUNNABLE正在运行,实际上Java没有RUNNABLE这个线程状态,把正在CPU上运行和随时调度到CPU上运行的都统称为RUNNABLE

后台线程:

        1)进程要结束(前台线程要结束),无力阻止

        2)后台线程先结束也不影响进程的结束(其他前台进程的结束)

前台进程:

        1)前台线程结束,此时进程就结束,后台线程也就随之结束

        2)前台线程不结束,后台线程结束了不影响

         一个进程中,前台线程可以有很多个(创建的线程默认就是前台的),必须所有的前台线程都结束,进程才能结束

        代码中,创建的new Thread对象生命周期和内核中的实际的线程是不一定一样的

        可能会出现Thread对象仍然存在,但是内核中的线程不存在的情况但是不会出现(Thread对象不存在,线程还在的情况)

        1.调用start之前,内核中还没创建线程

        2.线程的run执行完毕,内核的线程就没了,但是Thread对象仍然存在

2.3启动一个线程

        调用start方法,才真正的在操作系统的底层创建出一个线程.

        经典问题:start和run之间的区别

        start:调用系统函数,真正的在操作系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调出不同的api(windows,linux,mac...),创建好新的线程之后再单独执行run

        run:描述了线程要执行的的任务,也可以成为"线程的入口"

        start的执行速度一般是比较快的(创建线程 比较轻量)

        一旦start执行完毕,新线程就会开始执行,调用start的线程也会继续执行main

        两个线程分别个做个的

        调用start不一定非得是main线程调用的,任何线程都可以创建其他线程

如图:这里就是由thread调用了thread1线程

        如果系统资源充裕,就可以任意的创建线程,(当然,线程不是越多越好);

       

        一个Thread对象只能调用一次start,如果多次调用start就会出现问题(一个Thread对象只能对应系统中的一个线程)

IllegalThreadStateException 报错

        由于Java中希望一个Thread对象能够对应到一个系统中的线程,因此就会 start中根据线程状态做出判定:如果Thread对象是没有start,此时的状态是一个NEW状态,接下来可以顺利调用start,如果已经调用过start,就会进入其他状态,只要不是NEW状态,接下来执行start都会抛出异常

        

2.4程序的中断(程序的终止)

目前常见的有以下的两种方法:

1.通过共享的标记来进行沟通

2.调用interrupt()方法来停止

        例如:A,B两个线程,B正在运行,A想让B结束,核心在于A让B的run方法直接完毕,此时B就自然结束了,而不是说B的run执行一般,A直接把B强制结束了

用共享标记结束,例如:

    private static boolean isQuit=false;public static void main(String[] args) throws InterruptedException {Thread thread=new Thread(()->{while(!isQuit){System.out.println("hello thread");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();Thread.sleep(3000);System.out.println("main方法开始中断程序");isQuit=true;}

用isQuit来作为标志位

如果将isQuit放在main方法里面,作为局部变量,代码会报错

变量捕捉,是lambda表达式/匿名内部类的一个语法规则

        isQuit和lambda定义在一个作用域中,此时lambda是可以访问到lambda外部(和lambda同一个作用域的变量),Java的变量捕捉有特殊要求,要求捕捉的变量得是final/事实final

        写成成员变量之后就可以了是因为此时走的语法是"内部类访问外部类的成员"

        lambda表达式本质上是一个"函数式接口"产生的"匿名内部类"

        

2.调用interrupt来终止

1)interrupt方法能够设置标志位,也能唤醒sleep等阻塞的方法(会抛出异常)

2)sleep被唤醒后,会清空标志位

如图可以看出,标志位被sleep清空,并没有中断此线程

线程A和线程B,A希望B线程终止

1)如果B线程想无视A线程,就直接catch中啥也不做

B线程仍然会继续执行      

sleep清除标志位,就可以使B能做出这种选择,如果sleep不清楚标志位,B势必会结束,无法写出继续让线程继续的代码

2)如果B线程想立即结束就直接在catch中写上return或者break

此时B线程就会立即结束

3)如果B想稍后再结束,就可以在catch中写上一些其他的逻辑(比如释放资源,清除硬一些数据,提交一些结果....收尾工作)

这些逻辑完成之后再进行return/break;

这些给了程序员更多的操作空间

2.5等待一个线程-join()

操作系统针对多个线程的执行是一个"随机调度,抢占式执行"的过程

线程等待就是在确定两个线程的结束顺序

无法确定两个线程调度执行顺序,但是可以控制谁先结束,谁后结束

        让后结束的线程等待先结束的线程即可,此时后结束的线程会进入阻塞,一直到先结束的线程真的结束了,阻塞才结束

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,醉倒等millis毫秒

public boid join(long millis,int nanos)

一般不用,后面是精确到纳秒

同理,但可以更高精度

因为计算机很难做到精确计时,一般能精准到ms就不错了

和操作系统相关像windows,linux系统,线程调度开销比较大,操作系统中还有一类系统"实时操作系统"就能把调度开销尽可能降低,开销小于一定的误差要求,从而可以做到更精准

        

 Thread thread1=new Thread(()->{for(int i=0;i<3;i++){System.out.println("Hello thread1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread thread2=new Thread(()->{for(int i=0;i<4;i++){System.out.println("Hello thread2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});System.out.println("main开始");thread1.start();thread2.start();try {thread1.join();} catch (InterruptedException e) {e.printStackTrace();}try {thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main结束");

此代码为例,就是main线程等待thread1和thread2结束后才往下运行

当然不一定是main去等待,thread1也可以去等待thread2结束后再继续

代码如下

 public static void main(String[] args) {Thread thread1=new Thread(()->{for(int i=0;i<3;i++){System.out.println("Hello thread1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread thread2=new Thread(()->{try {thread1.join();} catch (InterruptedException e) {e.printStackTrace();}for(int i=0;i<4;i++){System.out.println("Hello thread2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});System.out.println("main开始");thread2.start();thread1.start();try {thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main结束");}

这里就是thread2中等待thread1

2.6获取当前线程的引用

方法说明
public static Thread currentThread();返回当前线程对象的引用

2.7休眠当前线程

线程执行sleep,就会使得这个线程不参与CPU调度,从而把资源让出来给别人使用

                                        "放权"

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程
public static void sleep(long millis,int nanos) throws InterruptedException可以更高精度精确到纳秒

3.线程的状态

进程的状态:

就绪:正在CPU上执行,或者随时可以去CPU上执行

阻塞:暂时不能去参与CPU执行

Java的线程对于状态做了更详细的区分,不仅仅是就绪和阻塞了

六种状态

1.NEW                                 这种Thread对象虽然有了,但是内核的线程还没有(还没有调用start)

 public static void main(String[] args) {//匿名内部类的lambda表达式创建Runnable子类对象,备份下Thread thread=new Thread(() -> {for (int i=0;i<3;i++){System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});System.out.println(thread.getState());thread.start();
}

2.TERMINATED                  当前Thread对象虽然还在,但是内核的线程已经销毁(线程已经结束了)

    public static void main(String[] args) {//匿名内部类的lambda表达式创建Runnable子类对象,备份下Thread thread=new Thread(() -> {for (int i=0;i<3;i++){System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(thread.getState());}

3.RUNNABLE                      就绪状态.正在CPU上运行+随时可以去CPU上去运行

public static void main(String[] args) {//匿名内部类的lambda表达式创建Runnable子类对象,备份下Thread thread=new Thread(() -> {while(true){}});thread.start();System.out.println(thread.getState());}

4.BKLOCKED                      因为锁竞争引起的阻塞

   public static void main(String[] args) throws InterruptedException {//匿名内部类的lambda表达式创建Runnable子类对象,备份下Thread thread1=new Thread(() -> {synchronized (Locker) {for (int i = 0; i < 50000; i++) {count++;}}});Thread thread2=new Thread(()->{synchronized(Locker){for(int i=0;i<50000;i++) {System.out.println(thread1.getState());count++;}}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(count);}

5.TIMED_WAITING              有超时时间的阻塞等待,比如sleep或者join带参数版本

public static void main(String[] args) {//匿名内部类的lambda表达式创建Runnable子类对象,备份下Thread thread=new Thread(() -> {while(true){System.out.println("hello thread");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(thread.getState());}

6.WAITING                            没有超时时间的阻塞等待,join/wait

    public static void main(String[] args) throws InterruptedException {//匿名内部类的lambda表达式创建Runnable子类对象,备份下Thread thread=new Thread(() -> {while(true){}});Thread thread2=new Thread(()->{try {thread.start();thread.join();} catch (InterruptedException e) {e.printStackTrace();}});thread2.start();Thread.sleep(3000);System.out.println(thread2.getState());}

        学习线程状态主要就是为了调试,比如遇到某个代码功能没有执行就可以观察对应线程的状态,看看是否是因为一些原因阻塞了.

相关文章:

JavaEE--2.多线程

1.认识线程(Thread) 1.1概念 1)什么是线程 ⼀个线程就是⼀个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码. 2)为什么要有线程 首先, "并发编程" 成为 "刚需". 1. 单核 CPU 的发展遇到…...

沐渥氮气柜控制板温湿度氧含量氮气流量四显智控系统

氮气柜控制板通常用于实时监控和调节柜内环境参数&#xff0c;确保存储物品如电子元件、精密仪器、化学品等&#xff0c;处于低氧、干燥的稳定状态。以下是沐渥氮气柜控制板核心参数的详细介绍及控制逻辑&#xff1a; 一、控制板核心参数显示模块 1&#xff09;‌温度显示‌&am…...

线性代数-矩阵的秩

矩阵的秩&#xff08;Rank&#xff09;是线性代数中的一个重要概念&#xff0c;表示矩阵中线性无关的行&#xff08;或列&#xff09;的最大数量。它反映了矩阵所包含的“有效信息”的维度&#xff0c;是矩阵的核心特征之一。 直观理解 行秩与列秩&#xff1a; 行秩&#xff1…...

【自然语言处理与大模型】模型压缩技术之量化

在这篇文章中想和大家分享什么是量化&#xff1f;为什么要量化&#xff1f;以及如何实现量化&#xff1f;通过这三个基本问题&#xff0c;我们不仅可以全面了解量化的内涵和外延&#xff0c;还能更清晰地认识到它在实践中的重要性和应用价值。 一、什么是量化呢&#xff1f; 大…...

OneClicker脚本自动运行工具

工作的时候&#xff0c;有很多琐碎的事情需要重复的做 比如打开某个文件&#xff0c;打开某个网站&#xff0c;打开某个软件 这个时候可以写个自动脚本&#xff0c;把机械琐碎的事情交给脚本处理 但是脚本一多&#xff0c;不好管理&#xff0c;而且要选择哪个脚本也是个麻烦的事…...

Activity之间交互

Backgroud&#xff1a; 想要实现Activity之间的交互&#xff0c;需要用到intent工具 本博客中所有第二Activity均为SecActivity&#xff0c;需要预先进行创建 本博客所使用的开发语言为Kotlin 使用intent调用Activity 显式调用 val intent Intent(this, SecActivity::class.…...

【React】搜索时高亮被搜索选中的文案

文章目录 代码实现 代码实现 函数封装&#xff1a; export function highlightKeyword(input: string, keyword: string | undefined) {if (!keyword || !input || !input.includes(keyword)) return input;const startIndex input.indexOf(keyword);return React.createEle…...

MCP(Minecraft Coder Pack)完全指南:从入门到精通

1. 什么是MCP&#xff1f; Minecraft Coder Pack&#xff08;简称MCP&#xff09;是一套用于反编译、修改和重新编译Minecraft Java版源代码的工具集。它允许开发者深入研究Minecraft的底层代码&#xff0c;并在此基础上进行模组&#xff08;Mod&#xff09;开发、代码分析或自…...

stm32week12

stm32学习 九.stm32与HAL库 2.HAL库框架 总架构&#xff1a; 文件介绍&#xff1a; ppp是某一外设&#xff0c;ex是拓展功能 HAL库API函数和变量命名规则&#xff1a; HAL库对寄存器位操作的相关宏定义&#xff1a; HAL库的回调函数&#xff1a; 3.STM32启动过程 MDK编译过…...

mindspeed-rl使用注意事项

1、安装 参考1&#xff1a;docs/install_guide.md R1-CLM/MindSpeed-RL - Gitee.com 参考2&#xff1a;VLLM x Ascend框架_vllm-ascend-CSDN博客 2、SFT微调 整体参考docs/supervised_finetune.md 自定义数据格式同&#xff1a;AUTO-DL 910B mindspeed-llm 4层DeepSeek …...

第 4 篇:平稳性 - 时间序列分析的基石

第 4 篇&#xff1a;平稳性 - 时间序列分析的基石 在上一篇中&#xff0c;我们学习了如何将时间序列分解为趋势、季节性和残差。我们看到&#xff0c;很多真实世界的时间序列&#xff08;比如 CO2 浓度&#xff09;都包含明显的趋势&#xff08;长期向上或向下&#xff09;和/…...

KRaft面试思路引导

Kafka实在2.8之后就用KRaft进行集群管理了 Conroller负责选举Leader&#xff0c;同时Controller管理集群元数据状态信息&#xff0c;并将元数据信息同步给各个分区的Leader 和Zookeeper管理一样&#xff0c;会选出一个Broker作为Controller去管理整个集群&#xff0c;但是元数…...

怎么建立自然语言领域的评价标准

怎么建立自然语言领域的评价标准 明确评价目标与对象 首先要清晰界定评价的目标,比如是评估模型对文本语义的理解能力、生成文本的质量,还是系统在信息检索中的表现等。同时,明确评价对象,可能是一个语言模型、一个问答系统、一个机器翻译工具等。确定评价维度与指标 语言…...

EMQX学习笔记

MQTT简介 MQTT是一种基于发布订阅模式的消息传输协议 消息&#xff1a;设备和设备之间传输的数据&#xff0c;或者服务和服务之间传输的数据 协议&#xff1a;传输数据时所遵循的规则 轻量级&#xff1a;MQTT协议占用的请求源较少&#xff0c;数据报文较小 可靠较强&#xff…...

组件是怎样写的(1):虚拟列表-VirtualList

本篇文章是《组件是怎样写的》系列文章的第一篇&#xff0c;该系列文章主要说一下各组件实现的具体逻辑&#xff0c;组件种类取自 element-plus 和 antd 组件库。 每个组件都会有 vue 和 react 两种实现方式&#xff0c;可以点击 https://hhk-png.github.io/components-show/ …...

CGAL 计算直线之间的距离(3D)

文章目录 一、简介二、实现代码三、实现效果一、简介 这里的计算思路很简单: 1、首先将两个三维直线均平移至过原点处,这里两条直线可以构成一个平面normal。 2、如果两个直线平行,那么两条直线之间的距离就转换为直线上一点到另一直线的距离。 3、如果两个直线不平行,则可…...

定期检查滚珠丝杆的频率是多久?

定期检查滚珠丝杆的频率通常是每半年进行一次‌&#xff0c;根据不同的使用环境和设备类型&#xff0c;滚珠丝杆的检查周期有所不同。接下来我们一起看看滚珠丝杆的维护保养方法&#xff1a;‌ 1、‌清洗‌&#xff1a;每隔一段时间对滚珠丝杆进行清洁&#xff0c;将滚珠丝杆拆…...

Spark-SQL连接Hive全攻略

在大数据处理领域&#xff0c;Spark-SQL与Hive的结合能发挥强大的功能。今天就来给大家分享一下Spark-SQL连接Hive的多种方式。 Spark SQL编译时可选择包含Hive支持&#xff0c;这样就能使用Hive表访问、UDF、HQL等特性&#xff0c;而且无需提前安装Hive。其连接方式丰富多样…...

在Ubuntu 18.04下编译OpenJDK 11

在Ubuntu 18.04下编译OpenJDK 11 源码下载地址&#xff1a; 链接: https://pan.baidu.com/s/1QAdu-B6n9KqeBakGlpBS3Q 密码: 8lho Linux下的环境要求 不同版本的jdk会要求在不同版本的Ubuntu下编译&#xff0c;不要用太高版本的Ubuntu或者gcc&#xff0c;特别是gcc&#xf…...

Spring MVC 一个简单的多文件上传

原始代码逐行解释 PostMapping("/uploads") // ① 声明处理POST请求&#xff0c;路径为"/uploads" ResponseBody // ② 直接返回数据到响应体&#xff0c;不进行视图解析 public String uploads(MultipartFile[] files, // …...

FreeRTos学习记录--1.工程创建与源码概述

1.工程创建与源码概述 1.1 工程创建 使用STM32CubeMX&#xff0c;可以手工添加任务、队列、信号量、互斥锁、定时器等等。但是本课程不想严重依赖STM32CubeMX&#xff0c;所以不会使用STM32CubeMX来添加这些对象&#xff0c;而是手写代码来使用这些对象。 使用STM32CubeMX时&…...

Vmware esxi 给现有磁盘增加空间后并扩展系统里磁盘空间

当前EXSI上虚拟机所在的单独数据磁盘空间满了&#xff0c;需要对空间进行扩容&#xff0c;我们先在主机对磁盘容量进行调整&#xff0c;然后在系统里面对磁盘空间进行拓展&#xff0c;这些操作需要保留数据并且不改变现有的磁盘格局。 遵循大致操作流程是&#xff1a; 1.先登录…...

Linux基础学习--linux的文件权限与目录配置

linux的文件权限与目录配置 1.用户与用户组 在Linux中&#xff0c;每个文件都有相当多的属性和权限&#xff0c;其中最重要的概念就是文件的拥有者。 1.1 文件拥有者 Linux是一个多人多任务的系统&#xff0c;常常有多人共用一台主机的情况出现&#xff0c;因此在系统中可以…...

LLM大模型中的基础数学工具—— 约束优化

Q26: 推导拉格朗日乘子法 的 KKT 条件 拉格朗日乘子法与 KKT 条件是啥&#xff1f; 拉格朗日乘子法是解决约束优化问题的利器。比如&#xff0c;想最小化函数 &#xff0c;同时满足约束 &#xff0c;就构造拉格朗日函数 &#xff08; 是乘子&#xff09;。KKT 条件是解这类问…...

涨薪技术|0到1学会性能测试第20课-关联技术

前面的推文我们掌握了性能测试脚本开发参数化技术一系列知识,今天开始给大家分享关联技术知识,后续文章都会系统分享干货,带大家从0到1学会性能测试! 关联是LoadRunner中一个很重要的应用,对于初学者来说也是最容易犯错的地方,但是很遗憾的是,并没有任何特定的错误与关联…...

SpringAI入门示例

AI编程简介 纯Prompt模式 纯Prompt模式是AI编程中最基础的交互架构。用户通过输入自然语言文本&#xff08;即Prompt&#xff09;向AI模型发出指令&#xff0c;模型依据自身预训练所积累的知识和语言理解能力&#xff0c;直接生成相应的文本响应。其工作原理是&#xff0c;用…...

SQL 中 ROLLUP 的使用方法

ROLLUP 是 SQL 中一种分组操作&#xff0c;它生成多个分组集的小计行和总计行&#xff0c;提供层次化的汇总数据。 基本语法 SELECT column1, column2, ..., aggregate_function(column) FROM table GROUP BY ROLLUP (column1, column2, ...); 使用示例 假设有一个销售表 sal…...

Web前端:Overflow属性(超出裁剪属性)

一、什么是 Overflow&#xff1f; 在网页布局中&#xff0c;容器&#xff08;如 <div>、<section> 等&#xff09;通常有固定尺寸&#xff08;如 width 和 height&#xff09;。当容器内的内容&#xff08;文本、图片等&#xff09;超出容器边界时&#xff0c;就会…...

20250421在荣品的PRO-RK3566开发板的Android13下使用io命令控制GPIO

20250421在荣品的PRO-RK3566开发板的Android13下使用io命令控制GPIO 2025/4/21 10:44 【本文只打开了io命令。通过io控制GPIO放到下一篇了】 缘起&#xff1a;需要在荣品的PRO-RK3566开发板的Android13的u-boot中来控制GPIO3A1【配置以太网RTL8211F-CG】。 直接使用GPIO库函数 …...

20250421在荣品的PRO-RK3566开发板的Android13下频繁重启RKNPU fde40000.npu: Adding to iommu gr

20250421在荣品的PRO-RK3566开发板的Android13下频繁重启RKNPU fde40000.npu: Adding to iommu gr 2025/4/21 14:50 缘起&#xff1a;电池没电了&#xff0c;导致荣品的PRO-RK3566的核心板频繁重启。 内核时间4s就重启。100%复现。 PRO-RK3566 Android13启动到这里 复位&#…...

在 8MHz 的时钟电路中挂接电阻,电容

匹配电阻&#xff1a;在晶体振荡电路中&#xff0c;用于匹配晶体和振荡电路的阻抗&#xff0c;确保振荡的稳定性&#xff0c;阻值通常在几十千欧到几百千欧&#xff0c;例如 1MΩ、33KΩ、47KΩ 等。 在一些电子电路中&#xff0c;尤其是涉及到时钟信号的产生和传输时&#xf…...

卸载工具:IObit Uninstaller Pro v14.3.0 中文绿色专业便携版

IObit Uninstaller 是一种功能强大的卸载工具&#xff0c;可帮助您快速方便地从计算机中移除不需要的程序和文件夹。它不仅仅可以从计算机中卸载应用程序&#xff0c;还可以移除它们的卸载残留。可以检测和分类所有已安装的程序&#xff0c;并可以批量卸载&#xff0c;只需一键…...

【目标检测】目标检测综述 目标检测技巧

I. 目标检测中标注的关键作用 A. 目标检测数据标注的定义 目标检测是计算机视觉领域的一项基础且核心的任务&#xff0c;其目标是在图像或视频中准确识别并定位出预定义类别的目标实例 1。数据标注&#xff0c;在目标检测的语境下&#xff0c;指的是为原始视觉数据&#xff0…...

c++基础·move作用,原理

目录 一、代码结构概览 二、逐层解析实现逻辑 1. 模板参数推导 2. 返回类型设计 3. 类型转换逻辑 三、关键特性与设计思想 1. 移动语义的本质 2. 为何必须用 remove_reference 3. 万能引用的兼容性 四、边界场景与注意事项 1. 对 const 对象的处理 2. 返回值优化&a…...

考研系列-计算机网络-第四章、网络层

一、网络层的概述和功能 1.功能概述 2.SDN的基本概念...

服务器在国外国内用户访问慢会影响谷歌排名吗?

谷歌明确将“页面加载速度”和“用户体验”作为排名核心指标&#xff0c;但当服务器物理距离过远时&#xff0c;国内用户动辄3秒以上的加载延迟&#xff0c;可能导致跳出率飙升、爬虫抓取困难等连锁反应。 但盲目将服务器迁回国内&#xff0c;又会面临备案成本、运维门槛等新难…...

iFable,AI角色扮演互动平台,自动生成沉浸式故事游戏

iFable是什么 iFable 是一个以动漫角色为主题的互动角色扮演游戏平台&#xff0c;旨在为用户提供沉浸式的故事冒险体验。平台允许玩家通过简单的创意输入&#xff0c;利用AI技术生成独特的互动故事与游戏体验。iFable 的设计宗旨在于帮助玩家与虚拟角色建立情感连接&#xff0…...

Nginx反向代理用自定义Header参数

【啰嗦两句】 也不知道为啥&#xff0c;我仅仅想在Nginx的反向代理中使用自己定义的“x-api-key”做Header参数&#xff0c;却发现会被忽略&#xff0c;网上搜的资料都是说用“proxy_set_header”&#xff0c;却只愿意介绍最基本的几个参数&#xff0c;你懂的&#xff0c;那些资…...

Spark SQL概述(专业解释+生活化比喻)

专业解释 一、什么是Spark SQL&#xff1f; 一句话定义&#xff1a; Spark SQL是Apache Spark中专门处理结构化数据的模块&#xff0c;可以让你像操作数据库表一样处理数据&#xff0c;支持用SQL查询或编程API&#xff08;DataFrame/DataSet&#xff09;分析数据。 通俗理解…...

LX3-初识是单片机

初识单片机 一 什么是单片机 单片机:单片微型计算机单片机的组成:CPU,RAM(内存),flash(硬盘),总线,时钟,外设…… 二 Coretex-M系列介绍 了解ARM公司与ST公司ARM内核系列: A 高性能应用,如手机,电脑…R 实时性强,如汽车电子,军工…M 超低功耗,如消费电子,家电,医疗器械 三…...

第二章 Logback的架构(一)

Logback的架构 Logback作为一个通用框架&#xff0c;可以应对不同场景的日志记录。目前&#xff0c;Logback 被划分为三个模块&#xff1a;logback-core、logback-classic 和 logback-access。 Logback的core模块为其他两个模块提供基础支持。classic模块扩展了core模块&…...

开发指南:构建结合数字孪生、大语言模型与知识图谱的智能设备日志分析及生产异常预警系统

1. 引言&#xff1a;数字孪生、大语言模型与知识图谱在智能制造中的融合 智能制造和工业4.0的浪潮正在重塑全球制造业格局&#xff0c;其核心在于利用先进的数字技术实现生产过程的实时决策、效率提升、灵活性增强和敏捷性改进。在这一转型过程中&#xff0c;数字孪生&#xf…...

【TeamFlow】4.1 Git使用指南

以下是 Git 在 Windows 系统上的配置和使用指南&#xff0c;包含详细步骤和注意事项&#xff1a; 安装 Git for Windows 下载与安装 前往 Git 官网 下载 Windows 版安装包 双击安装&#xff0c;关键选项建议&#xff1a; 选择 Use Git from Git Bash only&#xff08;推荐&…...

HADOOP 3.4.1安装和搭建(尚硅谷版~)

目录 1.配置模版虚拟机 2.克隆虚拟机 3.在hadoop102安装JDK 4.完全分布式运行模式 1.配置模版虚拟机 1.安装模板虚拟机&#xff0c;IP地址192.168.10.100、主机名称hadoop100、内存2G、硬盘20G&#xff08;有需求的可以配置4G内存&#xff0c;50G硬盘&#xff09; 2.hado…...

通过Docker Desktop配置OpenGauss数据库的方法(详细版+图文结合)

文章目录 通过Docker Desktop配置OpenGauss数据库的方法**一、下载Docker Desktop&#xff0c;并完成安装**docker官网&#xff1a;https://www.docker.com/ **二、下载OpenGauss压缩包**安装包下载链接&#xff1a;https://opengauss.obs.cn-south-1.myhuaweicloud.com/7.0.0-…...

文件有几十个T,需要做rag,用ragFlow能否快速落地呢?

一、RAGFlow的优势 1、RAGFlow处理大规模数据性能&#xff1a; &#xff08;1&#xff09;、RAGFlow支持分布式索引构建&#xff0c;采用分片技术&#xff0c;能够处理TB级数据。 &#xff08;2&#xff09;、它结合向量搜索和关键词搜索&#xff0c;提高检索效率。 &#xf…...

SystemVerilog语法之内建数据类型

简介&#xff1a;SystemVerilog引进了一些新的数据类型&#xff0c;具有以下的优点&#xff1a;&#xff08;1&#xff09;双状态数据类型&#xff0c;更好的性能&#xff0c;更低的内存消耗&#xff1b;&#xff08;2&#xff09;队列、动态和关联数组&#xff0c;减少内存消耗…...

TensorFlow和PyTorch学习原理解析

这里写目录标题 TensorFlow和PyTorch学习&原理解析TensorFlow介绍原理部署适用场景 PyTorch介绍原理部署适用场景 Keras模型格式SavedModelONNX格式 TensorFlow和PyTorch学习&原理解析 TensorFlow 介绍 由 Google Brain 团队开发并于 2015 年开源。由于 Google 的强…...

悬空引用和之道、之禅-《分析模式》漫谈57

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第5章“对象引用”原文&#xff1a; Unless you can catch all such references, there is the risk of a dangling reference, which often has painful con…...

江湖密码术:Rust中的 bcrypt 加密秘籍

前言 江湖险恶,黑客如雨,昔日密码“123456”早被各路大侠怒斥为“纸糊轻功”。若还执迷不悟,用明文密码闯荡江湖,无异于身披藏宝图在集市上狂奔,目标大到闪瞎黑客双眼。 为护你安然度过每一场数据风波,特献上一门绝学《Rust加密神功》。核心招式正是传说中的 bcrypt 密…...