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

JavaEE之线程初阶(上)

前文我们知道计算机中的每一个程序都对应着一个进程,进程是CPU申请资源的最小单位,那么线程是什么呢,线程中我们又能学习到什么新的知识呢?? 我们来一探究竟

1. 认识线程(Thread)

  1. 线程是什么

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

  1. 为啥要有线程

⾸先,“并发编程"成为"刚需”,其次,多进程处理多个业务时,一个进程要经过多个步骤,创建进程,申请资源,加入PCB 链表,销毁进程,释放资源,把PCB 从链表中删除,对系统的性能影响比较大,涉及到内存和文件资源过多,所以我们就想要在一个进程申请了一份资源后,能不能有某个东西可以共享进程申请来的资源呢?所以我们引出了线程的概念,所以线程也叫做轻量级的进程

  • 单核CPU的发展遇到了瓶颈.要想提高算力,就需要多核CPU.而并发编程能更充分利用多核CPU资源
  • 有些任务场景需要"等待IO",为了让等待IO的时间能够去做⼀些其他的⼯作,也需要用到并发编程.
  • 其次,虽然多进程也能实现并发编程,但是线程比进程更轻量.

线程较于进程的优点:

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

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

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

  1. 进程和线程的区别
  • 进程是包含线程的.每个进程至少有⼀个线程存在,即主线程。
  • 进程和进程之间不共享内存空间.同⼀个进程的线程之间共享同⼀个内存空间.
  • 进程是系统分配资源的最小单位,线程是CPU调度的最小单位。
  • ⼀个进程挂了⼀般不会影响到其他进程.但是⼀个线程挂了,可能把同进程内的其他线程⼀起带⾛(整个进程崩溃).
  1. Java的线程和操作系统线程的关系
    线程是操作系统中的概念.操作系统内核实现了线程这样的机制,并且对⽤⼾层提供了⼀些API供用户使用(例如Linux的pthread库).
    Java 标准库中Thread类可以视为是对操作系统提供的API进行了进⼀步的抽象和封装

2. 如何创建多线程程序

2.1 方法1继承Thread类

public class Thread_lesson01_01 {public static void main(String[] args) {MyThread01 myThread01=new MyThread01();myThread01.start();}
}class MyThread01 extends Thread{@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("my thread...");}}
}

在这里插入图片描述

2.2 方法2实现Runnable接口

//创建Runnable接口
class MyThread02 implements Runnable{@Overridepublic void run() {while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("my thread...");}}
}public class Thread_lesson01_02 {public static void main(String[] args)  {//创建Thread类实例,调⽤Thread的构造⽅法时将Runnable对象作为target参数.Thread thread=new Thread(new MyThread02());thread.start();}
}

在这里插入图片描述

2.3 匿名内部类实现线程

2.3.1 匿名内部类创建Thread子类对象

public class Thread_lesson01_05 {public static void main(String[] args) {Thread thread=new Thread(){@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("匿名内部类创建Thread子类对象");}}};thread.start();}
}

2.3.2 匿名内部类创建Runnable子类对象

public class Thread_lesson01_06 {public static void main(String[] args) {Thread thread=new Thread(new Runnable() {@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("使用匿名类创建Runnable子类对象");}}});thread.start();}
}

2.3.3 lambda表达式创建Runnable子类对象

public class Thread_lesson01_04 {public static void main(String[] args) {Thread thread=new Thread(()->{while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("通过匿名lambda表达式创建 my thread...");}});thread.start();}
}

在了解了如何创建线程对象后,我们产生了几个问题

1.我们的任务是用run()方法重写的,为什么启动线程用的是start(),没有调用run()方法,为什么run()方法执行了??

在这里插入图片描述

2.我们是否可以直接调用run()方法而不通过start()呢??

这就要从start()方法和run()方法的区别来说了:

1.start()方法,真实的申请系统线程PCB,从而启动一个线程,参与CPU调度
2.run()方法,定义线程时指定线程要执行的任务,如果调用只是Java对象的一个普通方法而已

3.当进程中有两个或多个线程时,运行结果如何??产生这种结果是为什么??

我们通过一段代码来测试一下:

public class Thread_lesson01_03 {public static void main(String[] args) {//创建MyThread01线程实例MyThread03 myThread03=new MyThread03();//调用start方法启动线程myThread03.start();while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hhh main thread...");}}
}
class MyThread03 extends Thread{@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hhh my thread...");}}
}

在这里插入图片描述


3. Thread的类及常见方法

Thread类是JVM 用来管理线程的一个类,每个线程都有唯一的Thread对象与之关联

3.1 Thread的常见构造方法

方法
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target,String name)使用 Runnable 对象创建线程对象 ,并命名

我们使用一段代码来演示这四种常见的构造方法:

public class Thread_lesson02_01 {public static void main(String[] args) throws InterruptedException {MyThread myThread=new MyThread();//方法1:Thread() Thread t1 = new Thread(()->{while (true){System.out.println("Thread()...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//方法2: Thread(Runnable target)Thread t2 = new Thread(()->{while (true){System.out.println(Thread.currentThread().getName()+"Thread(Runnable target)...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//方法3:Thread(String name)Thread t3 = new Thread(()->{while (true){System.out.println(Thread.currentThread().getName()+"Thread(String name)...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"我是Thread(String name)");//方法4:Thread(Runnable target,String name)Thread t4 = new Thread(myThread,"我是Thread(Runnable target,String name)");//启动线程t1.start();t2.start();t3.start();t4.start();}
}class MyThread implements Runnable{@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("使用 Runnable 对象创建线程对象");}}
}

在该代码运行的情况下,我们使用JDK中提供的工具查看当前创建的线程——>jconsole
两种方法打开该工具:

1.在cmd命令行窗口输入jconsole

在这里插入图片描述

2. 在JDK 的bin目录下双击打开

在这里插入图片描述

打开查看线程的工具之后,我们来看一下线程的名字

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在线程中如果不为线程取别名,线程名默认从Thread-0开始到Thread()-N,在开发环境中,我们建议为每一个线程取上别名,方便日后查找问题

3.2 Thread的几个常见属性

属性获取方法说明
IDgetId()JVM默认为线程对象产生的一个编号,是Java层面的,与PCB 区分开
名称getName()
状态getState()Java层面定义的状态(NEW、 RUNNABLE、WAITING等等),与PCB 区分开
优先级getPriority()优先级⾼的线程理论上来说更容易被调度到
是否后台程序isDaemon()线程分为前台线程和后台线程,通过这个标识位来区分当前线程是前台还是后台
是否存活isAlive()表示的是系统中PCB是否销毁,与thread对应没啥关系
是否被中断isInterrupted()通过设置一个标志位让线程在执行时判断是否要退出
public class Thread_lesson02_02 {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还活着");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}// 退出循环System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(" ID: " + thread.getId());System.out.println(" 名称: " + thread.getName());System.out.println(" 状态: " + thread.getState());System.out.println(" 优先级: " + thread.getPriority());System.out.println(" 后台线程: " + thread.isDaemon());System.out.println(" 活着: " + thread.isAlive());System.out.println(" 被中断: " + thread.isInterrupted());// 启动线程,申请真正的PCB,参与CPU调度thread.start();System.out.println(" 状态: " + thread.getState());}
}

在这里插入图片描述

3.3 启动一个线程 -start()

调⽤start⽅法,才真的在操作系统的底层创建出⼀个线程.

在这里插入图片描述

3.4 中断一个线程

为什么会存在中断线程这个方法呢??
比如:小明在网上遭到电信诈骗,警察蜀黍发现了,让转账中断,那么警察蜀黍该怎么通知他呢??这就涉及到我们的停止线程的方式了。

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

1.通过共享的标记来进行沟通
2.调⽤interrupt()方法来通知

示例-1: 使⽤自定义的变量来作为标志位.

需要给标志位上加volatile关键字

示例-2: 使用Thread.interrupted()或者Thread.currentThread().isInterrupted()代替⾃定义标志位.

3.5 等待一个线程-join()

有时,我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。例如,小明上课要交学费,等小明交了学费才能上学,这时我们需要⼀个⽅法明确等待线程的结束。

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等millis毫秒
public void join(long millis,int nanos)同理,但可以更高精度

我们通过一段代码来说明为什么要使用等待线程-join():

我们对一个数进行累加10亿次,查看使用串行执行和并行执行所需要的时间:

public class Thread_lesson02_03 {//5亿 public static long count=500000000;public static void main(String[] args) throws  {// 串行serial();// 并行concurrency();}/*** 串行*/public static void serial(){long begin = System.currentTimeMillis();long a=0l;for (int i = 0; i < count; i++) {a++;}for (int i = 0; i < count; i++) {a++;}long end=System.currentTimeMillis();System.out.println("串行耗费时间"+(end-begin));}/*** 并行*/public static void concurrency()  {long begin = System.currentTimeMillis();Thread thread1=new Thread(()->{long a=0l;for (int i = 0; i < count; i++) {a++;}});thread1.start();Thread thread2=new Thread(()->{long a=0l;for (int i = 0; i < count; i++) {a++;}});thread2.start();long end=System.currentTimeMillis();System.out.println("并行耗费时间"+(end-begin));}
}

在这里插入图片描述

这是因为此时并行耗费的时间并不是执行完的时间,而是创建两个线程所耗费的时间差

在这里插入图片描述

此时我们就需要使用join()等待线程完成任务,我们将上述代码加以修改:

//等待线程执行完毕
thread1.join();
thread2.join();

在这里插入图片描述

3.6 获取当前线程引用

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

3.7 休眠当前线程

我们常用的Thread.sleep(1000)就是休眠当前进程,让其缓慢执行

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

4. 线程的状态

4.1 观察线程的所有状态

线程的状态是⼀个枚举类型Thread.State

利用下列代码即可打印所有的线程状态:

    public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}

NEW:表示创建好了一个Java线程对象,安排好了任务,但是还没有启动
RUNNABLE:运行+就绪的状态,在执行任务时最常见的状态之一,在系统中有对应PCB
BLOCKED:等待锁的状态,阻塞中的一种
WAITING:没有等待时间,一直死等,直到被唤醒
TIMED_WAITING:指定了等待时间的阻塞状态,过时不侯
TERMINATED:结束,完成状态,PCB已经销毁,但是JAVA线程对象还在

4.2 线程状态之间的关系

在这里插入图片描述

5. 多线程带来的风险——线程安全

5.1 观察线程不安全

/*** 使用两个线程,让count自增到10w*/
public class Thread_lesson03_01 {//定义一个变量,让其自增10w次private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread thread1=new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});thread1.start();Thread thread2=new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});thread2.start();//等待线程结束thread1.join();;thread2.join();//结束后,打印计算的count值System.out.println("count="+count);}
}

在这里插入图片描述

5.2 线程安全的概念

想给出⼀个线程安全的确切定义是复杂的,
但我们可以这样认为:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的

5.3 线程不安全的原因

1. 线程调度是随机的

由于线程的执行顺序无法人为控制,抢占式执行是造成线程安全问题的主要原因而且我们解决不了,完全是CPU自己调度而且和CPU核数有关

2. 修改共享数据

  • 多个线程修改同一个变量,会出现线程安全问题
  • 多个线程修改不同的变量,不会出现线程安全问题
  • 一个线程修改一个变量,也不会出现线程安全问题

3. 原子性

我们在前面的MySQL学习过程中,知道了事务是有原子性的,比如对一个事务,如果不让他完全执行任务时,就对他操作,会造成幻读,不可读等等不好的效果,MySQL中我们通过隔离级别保证事务的原子性,原子性即 :事务要么全都执行,要么全都不执行
在线程中,我们知道⼀条Java语句不⼀定是原⼦的,也不⼀定只是⼀条指令
比如刚才我们看到的count++,其实是由三步操作组成的:

  1. 从内存把数据读到CPU (LOAD)
  2. 进行数据更新 (ADD)
  3. 把数据写回到CPU (STORE)

由于线程是抢占式执行的,此处通过时间线来助于理解线程的原子性:

在这里插入图片描述

在这里插入图片描述

下图,对线程的原子性助以理解:

在这里插入图片描述

由于执行CPU指令不是原子性的,导致这三条指令没有全部执行完成就被CPU调度走了
另外的线程加载到的值是一个原始值,当两个线程分别完成自增操作之后把值写回内存时发生了覆盖现象

4. 内存可见性

可见性指,⼀个线程对共享变量值的修改,能够及时地被其他线程看到.
Java虚拟机规范中定义了Java内存模型(JMM),如下图所示:
在这里插入图片描述

  1. 线程之间的共享变量存在主内存(MainMemory).
  2. 每⼀个线程都有自己的"⼯作内存"(WorkingMemory),且线程工作内存之间是隔离的,线程对共享变量的修改线程执行相互感知不到
  3. 当线程要读取⼀个共享变量的时候,会先把变量从主内存拷贝到⼯作内存,再从⼯作内存读取数据.
  4. 当线程要修改⼀个共享变量的时候,也会先修改⼯作内存中的副本,再同步回主内存.
  5. 工作内存是JAVA层面对物理层面的关于程序所使用到了寄存器的抽象
  6. 如果通过某种方式 让线程之间可以相互通信,称之为内存可见性

5.指令重排序

指令重排序概念:
重排序是编译器、JVM和CPU为了提高执行效率对指令顺序进行调整的现象。它在保证单线程语义不变的前提下,减少了读写操作,提升了程序运行速度,并且保证程序的运行结果是正确。重排序分为编译器优化、CPU重排序和内存系统的“重排序”。虽然带来性能提升,但也要注意其可能影响多线程环境中的数据一致性。

6. 如何解决线程不安全问题

1.线程的调度是随机执行的:硬件层面的,我们解决不了
2.修改共享数据:在真实业务场景中,很难避免多线程,提供效率,我们解决不了
3.原子性:我们可以通过🔒锁实现原子性,下文介绍
4.内存可见性:我们可以让进程之间通过一种通信关系,解决内存的不可见性
5.指令重排序:我们可以程序员直接指定那个先执行

能够解决 3、4、5 其中一项,我们的线程不安全问题就可以解决

7.synchronized 关键字

7.1 synchronized 的特性

synchronized 会起到互斥效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同⼀个对象synchronized就会阻塞等待.

  • 进⼊synchronized修饰的代码块,相当于加锁
  • 退出synchronized修饰的代码块,相当于解锁

在这里插入图片描述

synchronized⽤的锁是存在Java对象头⾥的。

在这里插入图片描述
可以粗略理解成,每个对象在内存中存储的时候,都存有⼀块内存表示当前的"锁定"状态(类似于厕所 的"有⼈/⽆⼈").

  • 如果当前是"⽆⼈"状态,那么就可以使⽤,使⽤时需要设为"有⼈"状态.
  • 如果当前是"有⼈"状态,那么其他⼈无法使用,只能排队

在这里插入图片描述
理解"阻塞等待"

针对每⼀把锁,操作系统内部都维护了⼀个等待队列.当这个锁被某个线程占有的时候,其他线程尝试进⾏加锁,就加不上了,就会阻塞等待,⼀直等到之前的线程解锁之后,由操作系统唤醒⼀个新的线程, 再来获取到这个锁.
注意:

  • 上⼀个线程解锁之后,下⼀个线程并不是⽴即就能获取到锁.⽽是要靠操作系统来"唤醒".这也就 是操作系统线程调度的⼀部分⼯作.
  • 假设有ABC三个线程,线程A先获取到锁,然后B尝试获取锁,然后C再尝试获取锁,此时B和C 都在阻塞队列中排队等待.但是当A释放锁之后,虽然B⽐C先来的,但是B不⼀定就能获取到锁, ⽽是和C重新竞争,并不遵守先来后到的规则.

7.2 synchronize关键字的魔力

我们通过下列代码来探究synchronize的魔力吧!


7.2.1 代码1:不添加synchronize关键字,查看计算结果

public class Thread_lesson04_01 {public static void main(String[] args) throws InterruptedException {//初始化累加对象Counter01 counter01=new Counter01();//创建两个线程对一个变量进时累加Thread thread1=new Thread(()->{counter01.increase();});Thread thread2=new Thread(()->{counter01.increase();});// 启动线程thread1.start();thread2.start();//等待线程thread1.join();thread2.join();System.out.println(counter01.count);}
}
class Counter01 {public int count=0;public void increase(){for (int i = 0; i < 50000; i++) {count++;}}
}

在这里插入图片描述


7.2.2 代码2:给increase方法加上synchronize关键字

public class Thread_lesson04_02 {public static void main(String[] args) throws InterruptedException {Counter02 counter=new Counter02();Thread thread1=new Thread(()->{counter.increase();});Thread thread2=new Thread(()->{counter.increase();});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("count="+counter.count);}
}
class Counter02 {public int count=0;public synchronized void increase(){for (int i = 0; i < 50000; i++) {count++;}}
}

在这里插入图片描述

此时,thread1线程先获取了锁,方法执行完成之后thread2线程再获取锁,这样的情况是一个单线程运行状态,是把多线程转换为一个单线程,从而解决线程安全问题,但并不是我们想要的效果


7.2.3 代码3:给increase方法中的代码块加上synchronize关键字

public class Thread_lesson04_03 {public static void main(String[] args) throws InterruptedException {Counter03 counter=new Counter03();Thread thread1=new Thread(()->{counter.increase();});Thread thread2=new Thread(()->{counter.increase();});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("count="+counter.count);}
}
class Counter03 {public int count=0;public  void increase(){//在真实业务中,在执行锁代码块之前有很多的数据获取或其他的可以并行执行的逻辑//1.从数据库中查询数据 selectAll()//2.对数据进行处理 build()//3.其他的不修改共享变量的方法//......//当执行到修改共享变量的逻辑时,再加锁//被锁修饰的代码块用{}包裹,其中()中可以是任何对象,使用this就是当前调用该方法的对象synchronized(this) {for (int i = 0; i < 50000; i++) {count++;}}}
}

在这里插入图片描述

虽然当前代码依旧是按串行执行了,但是在锁定的代码块前后,有其他的方法或代码可以进程执行


7.2.4 代码4:定义方法increase和方法increase1,increase方法用synchronize关键字修饰,线程1调用increase方法,线程2调用increase1方法

public class Thread_lesson04_04 {public static void main(String[] args) throws InterruptedException {Counter04 counter=new Counter04();Thread thread1=new Thread(()->{counter.increase();});Thread thread2=new Thread(()->{counter.increase1();});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println("count="+counter.count);}
}
class Counter04 {public int count=0;public synchronized void increase(){for (int i = 0; i < 50000; i++) {count++;}}public void increase1(){for (int i = 0; i < 50000; i++) {count++;}}
}

在这里插入图片描述

由于increase1方法不加锁,在对increase1方法方法执行时,increase1方法不需要锁即可执行,导致了线程不安全问题,具体情况如下:
在这里插入图片描述


总结 :关于synchronized
1.被 synchronized 修饰的代码会变成串行执行
2.synchronized 可以去修饰方法,也可以修饰代码块
3.被synchronized 修饰的代码并不是一次性在CPU上执行完,而是中途可能会被CPU调度走,但是只有当所有的指令执行完成之后才会释放锁
4.只给一个线程加锁,也会出现线程安全问题

了解完了synchronize关键字后,我们来谈谈何为锁,锁又有哪些知识呢??

8. 锁🔒

我们知道,事务的隔离级别是通过锁和MVCC机制保证的,那么Java当中锁是用什么来实现的呢?锁存放在哪里呢??

8.1 锁是如何解决线程安全问题的?

  1. 解决线程非原子性

通过synchronize给方法加锁解决了原子性问题

在这里插入图片描述

  1. 解决内存不可见性

后一个线程永远读到的是上一个线程存放到主内存的值,通过这样的方式实现了内存可见性,
并没有对内存可见性做技术上的处理

  1. 解决不了重排序问题

8.2 锁存放的位置

在Java虚拟机中,对象在内存中的结构可以划分为4个区域

  1. markword: 对象头:锁信息、GC(垃圾回收)次数,程序计数器,一般为8BYTE
  2. 类型指计:当时的对象是哪个类,一般为4BYTE
  3. 实例数据:成员变量,不定
  4. 对齐填充:一个对象所的占的内存必须是8byte的整数倍,根据实例数据确定

相关文章:

JavaEE之线程初阶(上)

前文我们知道计算机中的每一个程序都对应着一个进程&#xff0c;进程是CPU申请资源的最小单位&#xff0c;那么线程是什么呢&#xff0c;线程中我们又能学习到什么新的知识呢&#xff1f;&#xff1f; 我们来一探究竟 1. 认识线程&#xff08;Thread&#xff09; 线程是什么 …...

Spring Security @PreAuthorize注解

PreAuthorize 注解在 Spring Security 中提供了一种声明式的方法&#xff0c;可以在您的 Spring Boot 应用中添加方法级别的安全检查。本教程将引导您设置并有效使用 PreAuthorize&#xff0c;确保用户只能在具有特定角色或权限的情况下调用 REST API。 什么是 PreAuthorize&a…...

windows已建立威胁IP排查

在应急响应的时候&#xff0c;需要筛选出服务器建立连接的进程、PID&#xff0c;此代码可满足该需求实现共计2步 首先windos netstat-ano > all.txt&#xff0c; 上传至pycharm路径 第一步获取服务器建立连接的ip import re# 从文件读取 netstat 输出 def read_netstat_f…...

AI 大模型如何重塑软件开发流程?——技术革新与未来展望

人工智能的蓬勃发展为许多领域注入了强劲动力&#xff0c;而在软件开发这一关键技术领域&#xff0c;AI 大模型的应用正在彻底改变传统流程。从代码自动生成到智能测试&#xff0c;再到协同开发和流程优化&#xff0c;AI 正逐步成为软件开发者的得力助手&#xff0c;也推动企业…...

Admin.NET框架前端由于keep-alive设置缓存导致的onUnmount未触发问题

bug版本&#xff1a;next分支&#xff0c;基于.NET6版本&#xff1b; 问题描述&#xff1a; 1、添加keep-alive后&#xff0c;在其下运行的组件会出现onActived(被关注时)和onDeactived(取消关注时)生命周期&#xff0c;而组件原有生命周期为onMounted(被创造时)和onUnmounted(…...

Rest-assured

依赖 <--rest-assured依赖--><dependency><groupId>io.rest-assured</groupId><artifactId>rest-assured</artifactId><version>4.4.0</version><scope>test</scope> </dependency><--junit5依赖-->…...

ubuntu24挂载硬盘记录

1、显示硬盘及所属分区情况。在终端窗口中输入如下命令&#xff1a; sudo fdisk -l 找到自己硬盘的分区 我的地址/dev/sda 2、显示硬盘及所属分区情况。在终端窗口中输入如下命令&#xff0c;格式化自己硬盘&#xff1a; sudo mkfs -t ext4 /dev/sda 3、在终端窗口中输入如下…...

Dubbo集成SpringBoot实现远程服务调用

SpringBoot集成Dubbo Dubbo介绍了解 Dubbo 核心概念和架构dubbo特性dubbo运行原理图 SpringBoot集成Dubbo技术实战一、Dubbo Spring Boot 版本关系二、引入Maven依赖demo项目基础结构引入依赖创建每个模块1&#xff09;api模块2&#xff09;provider模块3&#xff09;consumer模…...

Redis最终篇分布式锁以及数据一致性

在前三篇我们几乎说完了Redis的所有的基础知识以及Redis怎么实现高可用性,那么在这一篇文章中的话我们主要就是说明如果我们使用Redis出现什么问题以及解决方案是什么,这个如果在未来的工作中也有可能会遇到,希望对看这篇博客的人有帮助,话不多说直接开干 一.Hotkey以及BigKey…...

TCP协议

报文格式 源/目的端口号&#xff1a;数据从哪个进程来&#xff0c;到哪个进程去32位序号&#xff1a;TCP传输过程中&#xff0c;在发送端出的字节流中&#xff0c;传输报文中的数据部分的每一个字节都有它的编号32位确认号&#xff1a;标识了报文接收端期望接收的字节序列4位首…...

Vue3 源码解析(十):watch 的实现原理

本篇文章笔者会讲解 Vue3 中侦听器相关的 api&#xff1a;watchEffect 和 watch 。在 Vue3 之前 watch 是 option 写法中一个很常用的选项&#xff0c;使用它可以非常方便的监听一个数据源的变化&#xff0c;而在 Vue3 中随着 Composition API 的写法推行也将 watch 独立成了一…...

2023年9月GESPC++一级真题解析

一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 题号 123456789101112131415 答案 CDBCDBACACBBDDA 1. 我们通常说的 “ 内存 ” 属于计算机中的&#xff08;&#xff09;。 A. 输出设备 B. 输 ⼊ 设备 C. 存储设备 D. 打印设备 【答案】 C 【考纲知识点】…...

vscode 远程连接ssh 密钥方式

目录 1. powershell 生成key&#xff1a; 2. 在服务器上安装公钥 linux测试成功&#xff1a; 3).为了确保连接成功&#xff0c;输入如下指令以保证以下文件权限正确&#xff1a; 3 开启 ssh 密钥登录 vscode 远程连接配置 python连接测试ok 查看日志&#xff1a; 1. po…...

字符函数和字符串函数

字符分类函数 C语言中有⼀系列的函数是专门做字符分类的&#xff0c;也就是⼀个字符是属于什么类型的字符的。 这些函数的使用都需要包含⼀个头文件&#xff1a;ctype.h 这些函数的用法非常类似。 int islower ( int c )islower是能够判断参数部分是否是小写字母的。 通过返…...

如何利用ChatGPT加速开发与学习:以BPMN编辑器为例

在现代开发中&#xff0c;开发者经常会遇到各种需要编写和学习新技术的任务。ChatGPT作为一种强大的自然语言处理工具&#xff0c;不仅可以辅助编写代码&#xff0c;还可以帮助学习新的编程概念和解决开发中的难题。本文将以开发一个BPMN&#xff08;业务流程建模与标注&#x…...

深度学习2

四、tensor常见操作 1、元素值 1.1、获取元素值 tensor.item() 返回tensor的元素&#xff1b;只能在一个元素值使用&#xff0c;多个报错&#xff0c;当存在多个元素值时需要使用索引进行获取到一个元素值时在使用 item。 1.2、元素值运算 tensor对元素值的运算&#xff1a;…...

工业生产安全-安全帽第二篇-用java语言看看opencv实现的目标检测使用过程

一.背景 公司是非煤采矿业&#xff0c;核心业务是采选&#xff0c;大型设备多&#xff0c;安全风险因素多。当下政府重视安全&#xff0c;头部技术企业的安全解决方案先进但价格不低&#xff0c;作为民营企业对安全投入的成本很敏感。利用我本身所学&#xff0c;准备搭建公司的…...

单片机学习笔记 9. 8×8LED点阵屏

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…...

c++ 力扣题(1)JZ64

JZ64求123...n_牛客题霸_牛客网 因此不能使用等差求和&#xff08;禁掉位运算为了禁掉等差求和&#xff0c;右移一位就是等差除二&#xff09;、循环、递归来解决 我们运用到在 统计构造次数所学到的内容&#xff1a; 1.可以利用静态全局变量的思想来做 2.构造n次对象&…...

logback动态获取nacos配置

文章目录 前言一、整体思路二、使用bootstrap.yml三、增加环境变量四、pom文件五、logback-spring.xml更改总结 前言 主要是logback动态获取nacos的配置信息,结尾完整代码 项目springcloudnacosplumelog&#xff0c;使用的时候、特别是部署的时候&#xff0c;需要改环境&#…...

基于零相差前馈补偿的 PID 控制

零相差前馈补偿是一种结合前馈补偿与反馈控制的策略&#xff0c;旨在提高控制系统对参考信号的跟踪精度。通过设计合理的前馈补偿器&#xff0c;使得系统对参考输入实现零相位差的跟踪&#xff0c;同时利用 PID 控制器保证系统的稳定性和动态性能。 1. 原理概述 目标&#xff…...

任务通知的本质(任务通知车辆运行) 软件定时器的本质(增加游戏音效)

任务通知的本质 没有任务通知 所谓"任务通知"&#xff0c;你可以反过来读"通知任务"。 我们使用队列、信号量、事件组等等方法时&#xff0c;并不知道对方是谁。使用任务通知时&#xff0c;可 以明确指定&#xff1a;通知哪个任务。 使用队列、信号量、…...

如何在 MySQL 中进行数据导入和导出?

在 MySQL 中进行数据的导入和导出是一项常见的任务&#xff0c;用于数据备份、恢复、迁移以及数据分析等多种用途。MySQL 提供了多种方法来进行数据的导入和导出&#xff0c;每种方法都有其适用的场景和特点。以下是几种常用的 MySQL 数据导入和导出方法&#xff0c;包括命令行…...

python语言基础-5 进阶语法-5.3 流式编程

声明&#xff1a;本内容非盈利性质&#xff0c;也不支持任何组织或个人将其用作盈利用途。本内容来源于参考书或网站&#xff0c;会尽量附上原文链接&#xff0c;并鼓励大家看原文。侵删。 5.3 流式编程&#xff08;参考链接&#xff1a;https://www.zhihu.com/question/59062…...

centos 服务器 docker 使用代理

宿主机使用代理 在宿主机的全局配置文件中添加代理信息 vim /etc/profile export http_proxyhttp://127.0.0.1:7897 export https_proxyhttp://127.0.0.1:7897 export no_proxy"localhost,127.0.0.1,::1,172.171.0.0" docker 命令使用代理 例如我想在使用使用 do…...

[个人专属博客] - docker安装

&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389;&#x1f389; 欢迎访问的个人博客&#xff1a;http://swzbk.site/&#xff0c;加好友&#xff0c;一起赚&#x1f9e7;&#x1f9e7;&#x1f9e7; &#x1f389;&#x1f389;&#x1f389;&#x1f389;&…...

推荐一个QDirStat基于 Qt 的目录统计工具

QDirStat 是一款基于 Qt 的目录统计工具&#xff0c;旨在帮助用户分析磁盘空间使用情况并进行清理。QDirStat的一些主要特点和功能&#xff1a; 跨平台兼容性&#xff1a;QDirStat 支持 Linux、BSD、Unix-like 系统以及 macOS&#xff0c;确保了广泛的用户基础。 直观的数据展…...

yolo自动化项目实例解析(九) 导航

比如我们经常使用的导航&#xff0c;说白了就是寻找两点之间最近的路径&#xff0c;也就是所谓的寻路&#xff0c;我们需要想办法让程序知道他要去哪里&#xff0c;路径包含&#xff08;起点、轨迹、终点&#xff09; 一、录制轨迹 从平面角度来看&#xff0c;我们可以把区域视…...

MySQL 报错:1137 - Can‘t reopen table

MySQL 报错&#xff1a;1137 - Can’t reopen table 1. 问题 对临时表查询&#xff1a; select a.ts_code,a.tsnum,b.tsnum from (select t.ts_code ,count(*) tsnum from tmp_table t group by t.ts_code having count(*) > 20 and count(*)< 50 ) a ,(select t.ts_…...

Bokeh实现大规模数据可视化的最佳实践

目录 引言 一、Bokeh简介 二、安装Bokeh 三、数据准备 四、性能优化 五、创建图表 六、添加交互功能 七、应用案例 八、高级技巧 九、总结 引言 在数据科学领域,数据可视化是一个至关重要的环节。通过可视化,我们可以直观地理解数据的特征和趋势,为数据分析和决策…...

HTMLCSS:比赛记分卡

效果演示 这段 HTML 和 CSS 代码创建了一个卡片式的体育比赛信息展示组件&#xff0c;用于显示篮球比赛的两个队伍名称、比赛时间、比分以及一些装饰性的视觉元素。 HTML <div class"card"><div data-status"inprogress" class"teams"…...

什么是 Faiss?

好的&#xff0c;我来详细解释 Faiss&#xff0c;它的用途、使用场景&#xff0c;以及如何安装和使用。 什么是 Faiss&#xff1f; Faiss 是由 Facebook AI Research 开发的一个开源库&#xff0c;专门用于高效的相似性搜索和聚类。它非常擅长在高维向量空间中进行快速搜索&a…...

【prism】遇到一个坑,分享!

背景 我通用prism的方式写了一个弹窗,弹窗绑定一个 Loaded 事件,但是Loaded事件一直不触发!!! 具体过程 我的loaded事件也是通过命令的方式绑定的: <i:Interaction.Triggers><i:EventTrigger EventName="Loaded...

vue制作代码比较工具

前两天朋友问我 有没有vue可以做一个json代码在线比较工具 我也是在网上搜了一下找到的 废话不说 直接上代码 采用 v3 pnpm i v-code-diff <div><CodeDiff:old-string"oldStr":new-string"newStr"output-format"side-by-side"/>…...

GPT系列文章

GPT系列文章 GPT1 GPT1是由OpenAI公司发表在2018年要早于我们之前介绍的所熟知的BERT系列文章。总结&#xff1a;GPT 是一种半监督学习&#xff0c;采用两阶段任务模型&#xff0c;通过使用无监督的 Pre-training 和有监督的 Fine-tuning 来实现强大的自然语言理解。在 Pre-t…...

Qt实现可拖拽的矩形

之前项目上需要用Qt来绘制可拖拽改变形状的矩形。看了Qt Graphics相关的内容&#xff0c;虽然对Qt怎么添加图元的有了些了解&#xff0c;但是具体如何实现拖拽效果&#xff0c;一时也没有什么好的想法。还好网上有人分享的例子&#xff0c;很受启发。后来又回顾了一下这部分的代…...

python爬虫初体验(五)—— 边学边玩小游戏

1. 打开浏览器 利用webbrowser 模块的 open()函数可以启动一个新浏览器&#xff0c;打开指定的 URL。 import webbrowser webbrowser.open(http://inventwithpython.com/) 2. 猜数字游戏 # -*- coding: utf-8 -*- # This is a guess the number game. import randomsecretN…...

学习日志015--python单链表

创建 class Node:def __init__(self,data):# 数据域self.data data# 链接域self.next Noneclass LinkList:def __init__(self,):# 初始化头节点self.head None# 记录链表的长度self.size 0 增加 #头插def insert_head(self,value):# 创建新节点node Node(value)q self…...

51WORLD与南京水利研究院联合研发,国产数字孪生超融合一体机

近日&#xff0c;太湖流域水治理国际会议在江苏省无锡市举行。大会由水利部国际合作与科技司、河湖管理司、中国水利学会、水利部太湖流域管理局、无锡市人民政府、中国交通建设集团有限公司指导&#xff0c;南京水利科学研究院主办&#xff0c;以“践行新发展理念、推进流域水…...

自动泊车变自动撞车?小米SU7遭遇批量事故

科技新知 原创作者丨依蔓 编辑丨蕨影 小米系统bug&#xff0c;70多辆小米SU7同一天自动泊车撞墙、撞柱&#xff01; 近日&#xff0c;多名车主反映小米汽车SU7标准版“自动泊车”功能出现故障&#xff0c;造成不同程度的撞击、剐蹭损伤。 小米客服此前回应涉事车主&#xff0…...

异常和中断

在计算机系统中&#xff0c;异常和中断是两种常见的用于处理异步事件的机制。以下是常见的异常和中断及其特点的详细解释&#xff1a; 异常&#xff08;内中断&#xff09; 异常&#xff0c;也称为内中断&#xff0c;是由CPU内部事件引起的中断。异常通常与程序执行的当前指令…...

代理IP在后端开发中的应用与后端工程师的角色

目录 引言 代理IP的基本概念和工作原理 代理IP在后端开发中的应用 网络爬虫与数据采集 负载均衡与性能优化 安全防护与隐私保护 后端工程师在使用代理IP时面临的挑战 结论 引言 在数字化时代&#xff0c;网络技术的飞速发展极大地推动了各行各业的发展。其中&#xff…...

设计模式之 观察者模式

观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听一个主题对象&#xff08;Subject&#xff09;。当主题对象的状态发生变化时&#xff0c;所有依赖于它的观察者都会得到…...

SQLSever显示物理和逻辑 IO活动量的相关信息及显示分析、编译和执行各语句所需的毫秒数。

SQLSever SET STATISTICS IO显示物理和逻辑 IO活动量的相关信息及SET STATISTICS TIME显示分析、编译和执行各语句所需的毫秒数。 1、 SET STATISTICS IO (Transact-SQL) 物理 IO 与访问磁盘上的数据页相关&#xff0c;逻辑 IO 与访问内存中的数据页&#xff08;数据缓存&…...

CSS3 动画:前端开发的动态美

CSS3 动画:前端开发的动态美 CSS3 动画是现代网页设计中不可或缺的一部分,它为静态的网页元素添加了动态效果,提升了用户体验。本文将深入探讨CSS3动画的基础知识、高级技巧,并展示如何在实际项目中应用这些动画。 CSS3 动画基础 CSS3动画主要通过@keyframes和动画属性(…...

JavaWeb之综合案例

前言 这一节讲一个案例 1. 环境搭建 然后就是把这些数据全部用到sql语句中执行 2.查询所有-后台&前台 我们先写后台代码 2.1 后台 2.2 Dao BrandMapper&#xff1a; 注意因为数据库里面的名称是下划线分割的&#xff0c;我们类里面是驼峰的&#xff0c;所以要映射 …...

基于Spring Boot+Unipp的博物馆预约小程序(协同过滤算法、二维码识别)【原创】

&#x1f388;系统亮点&#xff1a;协同过滤算法、二维码识别&#xff1b; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk17 前端&#xff1a; 技术&#xff1a;框…...

使用 Maven 构建一个简单的 Java 项目

Apache Maven 是一个强大的构建自动化工具&#xff0c;主要用于 Java 项目。它简化了构建和管理任何基于 Java 的项目的流程。 本指南将涵盖 Maven 的安装、设置一个简单的 Java 项目以及使用 Maven 运行该项目。 1. 安装 安装 Java 在安装 Maven 之前&#xff0c;需要确保…...

【51单片机】LCD1602液晶显示屏

学习使用的开发板&#xff1a;STC89C52RC/LE52RC 编程软件&#xff1a;Keil5 烧录软件&#xff1a;stc-isp 开发板实图&#xff1a; 文章目录 LCD1602存储结构时序结构 编码 —— 显示字符、数字 LCD1602 LCD1602&#xff08;Liquid Crystal Display&#xff09;液晶显示屏是…...

UDP协议

UDP&#xff08;UserDatagramProtocol&#xff09;是一个简单的传输层协议&#xff0c;特点&#xff1a;无连接、不可靠、面向数据包、全双工。 报文结构 1.源端口&#xff1a; 源端口号&#xff0c;需要对方回信时选用&#xff0c;不需要时全部置0. 2.目的端口&#xff1a;目…...