javaEE-线程的常用方法-4

目录

一.start():启动一个线程

调用start()方法

start()方法只能调用一次:

java中的API:

start()和run()的区别:

二.中断一个线程

中断线程方法1:引入标志位

中断线程方法2:调⽤interrupt()⽅法

抛出的异常:

三.等待一个线程 join()

四、获取线程引用

五。线程的状态

六、线程安全(重点,难点)

引起线程不安全的原因:

解决方法:

“加锁”

“可重入”性

死锁

java标准库中的线程安全类:

内存可见性



一.start():启动一个线程

之前我们已经看到了如何通过重写run⽅法创建⼀个线程对象但线程对象被创建出来并不意味着线程就开始运⾏了。重写run⽅法是提供给线程要做的事情的指令清单.

调用start()方法

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

start()方法只能调用一次:

对于同一个线程对象来说,start()方法只能调用一次.

若调用多次,除第一次的调用,之后的线程就会出现 illegalThreadStateException(非法线程状态异常)异常终止,而第一次调用的线程还能正常运行.

要想启动更多的线程,就要创建新的线程对象.

这里的main线程,和两个t1,t2 线程都是每隔一秒执行一次,可以看出打印结果 main和thread也是正如分析的一样.

在jconsloe.exe上也能看到三个线程的执行.

java中的API:

API 是应用程序编程接口(Application Programming Interface)的缩写。

 API 的结构:

在 Java 中,API(应用程序编程接口)指的是整个包、接口、类、方法等以及它们之间的关系和规范。API 是开发者用来构建应用程序的工具集,它包括了所有这些元素.

Java API ,指的是整个 Java 标准库,包括所有的包、接口、类、方法和异常等。这些元素共同构成了 Java 语言的核心功能,使得开发者能够构建各种应用程序,从简单的命令行工具到复杂的企业级应用。

它是一套预定义的函数、方法或类的集合,允许应用程序访问某些功能或数据,而无需关心底层的实现细节。API 为开发者提供了构建软件应用的积木。

start()和run()的区别:

start()方法:

调用start()是创建了一个新的线程:main线程,和t线程,两个线程同时工作,互不干扰。(并行执行)

通过Thread类调用start,开启一个线程,此时该线程处于就绪状态,并没有执行,一旦得到cpu的时间片,就自动调用run方法,开始执行。注意:无需等run方法结束,即可执行下面的代码。

run()方法

而若单调用run()方法,run()方法只是类的一个普通方法而已。只是在main()线程中,去执行了一个run()方法,该方法执行完后,再去执行后面的代码,属于串行执行。注意:这里不会创建新线程。

总结:run()就是一个普通的方法,而start()会创建一个新线程去执行run()的代码。

1、start方法用来启动相应的线程

2、run方法只是thread的一个普通方法,在主线程里执行;

3、需要并行处理的代码放在run方法中,start方法启动线程后自动调用run方法

4、run方法必去是public的访问权限,返回类型为void。

此处,调用run()方法后,就只能去执行run()方法中的代码,而main()方法调用run()方法后面的代码,只有执行完run()方法后,才能去执行。(属于一条路线)

参考博客:

线程中start方法和run方法的区别_线程start和run区别-CSDN博客

二.中断一个线程

一个线程在执行过程中,因某些需要,要让该线程中断,不再执行.就需要对该线程进行中断处理。

(就好像你在打游戏,突然来了个电话,就要先中断你的游戏,去接听电话)

方法1:引入标志位

通过共享的标记来进⾏沟通(这需要线程之间的代码逻辑的配合执行)

设置静态变量,通过对其修改,来实现中断线程的功能

注意:这里的isQuite是设置在全局变量处的,而不能设置在main线程中,

原因是run()方法是通过使用lambda表达式(匿名内部类)来实现的,但lambda函数中的变量要遵循变量捕获原则,就是内部用到的局部变量不能是可以修改的,而此处的isQuite又需要对其修改,因此不能设置成fianl类型的。

但lambda表达式可以访问到方法外定义的任意变量,因此, 就只能设置成全局变量了.

lambda表达式中,不允许存在可能被修改的变量的原因是:

这里结果,执行完"3s后 Thread线程结束",在将isQuite设置为true之前,又执行了一次t线程,才结束t线程.

这里执行完"3s后 Thread线程结束",直接将isQuite设置为true,结束t线程。

可见线程的执行顺序和执行时间是随机的.

方法2:调⽤interrupt()⽅法

isInterrupted():判定标志位

interrupt():设置标志位

将run方法内的循环条件设置为判定标志位,再在调用标志位,使其改变,达到中断线程的效果。

但这样在执行的时候会抛出一个异常

在sleep()函数,当主动让t线程结束(修改interrupted标志位)的时候,此时sleep()的执行还未结束,当sleep()被提前唤醒的时候,会自动清除interrupted标志位.就会出现矛盾:到底是让该线程结束,还是继续执行.

要不想让异常终止,只需要修改异常内容就可以.

抛出的异常:

旧版的idea是执行try-catah后,catch里的代码是自动打印调用栈.

新版的idea是执行try-catah后,catch中再抛出一个异常.

但是在实际开发过程中,catch对以上两种方法都不用,idea生成的这两种方法都不用,这只是一个站位的作用.

在实际开发中,catch代码块中实际可能会进行如下操作:

在java中.程序的终止,是一种"软性"操作.就是说,需要线程中的代码配合,才能达到中断的效果.

三.等待一个线程 join()

因为多线程是随机调度的,有时,我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。

为了实现这种效果,该方法就能解决这样的问题的。等待一个线程,指的是让一个线程执行结束,再进行之后的执行.

多用于一个线程不确定执行时间,且要等待该线程结束,再进行别的线程操作.

下面的代码实现这样的功能:在t线程中执行1到5的相加运算,再在main线程中将结果打印

package Thread_;public class Thread11 {private static int count;public static void main(String[] args) throws InterruptedException {//计算1--5相加,再在main线程中打印结果Thread t=new Thread(()->{for(int i=0;i<5;i++){count+=i;}System.out.println("Thread线程执行结束!");});t.start();t.join();//线程等待System.out.println("结果为: "+count);}
}

join()的功能:在哪个线程中调用被调用,就暂停该线程(进入阻塞状态),哪个线程调用该方法,就先执行哪个线程.

join()方法有一个受查异常InterruptedException,使用时需要处理。

上面的代码中,join方法在main线程中被调用,则main就进入阻塞状态。t线程调用了该方法,则要等t线程执行结束,才继续执行main线程。(就是说:main线程要等t线程执行结束之后,main才能继续执行)

就是因为阻塞,使这两个线程结束产生了先后关系。

//计算1--10000相加,分成两个线程执行,再在main线程中打印结果
private static int count;public static void main(String[] args) throws InterruptedException {//计算1--10000相加,分成两个线程执行,再在main线程中打印结果Thread t1=new Thread(()->{int n=0;//t1: 计算1到5000的相加for(int i=1;i<=5000;i++){n+=i;}count+=n;System.out.println("Thread线程执行结束!");});Thread t2=new Thread(()->{int n=0;for(int i=5001;i<=10000;i++){n+=i;}count+=n;System.out.println("Thread线程执行结束!");});t1.start();t2.start();t1.join();//线程等待t2.join();//线程等待System.out.println("结果为: "+count);}

此时的结果是正确的,但是若进行更大数字的相加时,

让其计算1到100亿数字的相加,算一下执行时间。

一个线程完成计算:

 private static long count;public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{for(long i=0;i<100_0000_0000L;i++){count+=i;}});t.start();long beg=System.currentTimeMillis();t.join();long end=System.currentTimeMillis();System.out.println("count= "+count);System.out.println("运算时间为: "+(end-beg));}

运行时间为3074ms.

 两个线程完成计算:t1负责完成前50亿的计算,t2负责完成后50亿的计算。

private static long count;public static void main(String[] args) throws InterruptedException {//计算1--100_0000_0000(100亿)相加,分成两个线程执行,再在main线程中打印结果Thread t1=new Thread(()->{long n=0;//t1: 计算1到5000的相加for(long i=1;i<=50_0000_0000L;i++){n+=i;}count+=n;System.out.println("Thread线程执行结束!");});Thread t2=new Thread(()->{long n=0;for(long i=50_0000_0001L;i<=100_0000_0000L;i++){n+=i;}count+=n;System.out.println("Thread线程执行结束!");});t1.start();t2.start();t1.join();//线程等待t2.join();//线程等待System.out.println("结果为: "+count);}

 

运行时间为1827ms.

双线程运算时间缩短,但此时也会存在线程安全的问题.

join()方法还有别的重载方法:

join():属于死等,t2线程不结束,就不会向下执行,

join(long millis):超时等待。在millis时间内,若t2线程没有结束,就不再等待,进行正常的代码流程。

第三个是设置一个ns级的时间,过于精确,用处不大。

要想把被join()阻塞的进程提前唤醒,也是可以通过interrupt()方法,将其唤醒。

四、获取线程引用

Thread.currenThread():获取当前线程的引用(Thread的引用)

如果是继承Thread,则可直接是由this调用;

若是实现Runnable接口或lambda表达式,此时this就不能代替Thread了,只能使用Thread.currendThread().

五。线程的状态

就绪状态:线程正在执行,或者随时准备着CPU的调用,执行的状态。

阻塞状态:线程暂时不方便去CPU上执行。

java中,线程有以下这几种状态:

1、NEW:Thread线程创建好了,但是还未调用start()方法。且直有处于NEW状态的线程才能调用start().

2、TERMINATED:Thread对象仍然存在,但是该线程已经执行完毕

3、RUNNABLE:就绪状态,线程正在执行,或者随时准备着CPU的调用,去CPU上执行。

4、TIMED_WAITING:指定时间的阻塞状态,达到一定时间后,自动解除阻塞

5、WAITING无时间限制的阻塞(死等),直有满足指定条件,才会结束阻塞。(join()/wain()都会进入WAITING状态)

6、BLOCK:由于锁竞争引起的阻塞。(存在线程安全的问题)

各状态的转换关系:

了解这些状态后,对代码的调试起到非常大的帮助

在jconsloe.exe中,也能看到线程的状态:

六、线程安全(重点,难点)

某个代码,若不论是在单线程下执行,还是在多线程下执行,都不会出现bug,这样的线程称为“线程安全”

若在单线程下运行正确,在多线程下,就可能产生bug,这样的线程就是称为“线程不安全”的,或叫存在“线程安全”问题

1.用一个线程计算1到10000的和,main线程打印结果:

结果正确.

2.用两个线程 t1计算1-5000的和,t2计算5001-10000的和,main打印结果:

该方法运行几次,发现每次的执行结果不确定,并且结果还是错误的。这就属于存在“线程安全问题”的代码。

这里的count++,在系统的底层其实是执行的三个cpu指令

1、load:从内存读取数据到cpu寄存器上。

2、add:将寄存器中的值+1.

3、save:将寄存器中的值写回内存中。

两个线程执行的三个cpu指令可能有各种顺序。

列出几种情况:

但是无数种情况中,只有在一个线程从load到save执行完毕后,再去执行下一个线程的load,才能得到正确结果。

在5万次的自增过程中,也不知道多少次是正确的执行顺序.这也是为啥采用两个线程计算时,每次的结果不但错误,且不一样.

引起线程不安全的原因:

1.操作系统上的线程是“抢占式线程”,“随即调度”的。这给线程之间的执行顺序带来了很多变数。(根本原因)

2、代码结构上:代码中存在多个线程同时修改一个变量

(一个线程修改一个变量,或多个线程读取一个变量,或是多个线程修改多个变量,这些都不会引起线程安全问题)

3.上面的线程修改操作(load->add->save),不是“原子的”操作(要莫不执行,要么执行完)(直接原因)

不是“原子的“指的是,一个线程上的这些指令,执行到一半,可能会被调度走,让其他线程继续执行。而每个cpu指令(load,add,save....单个来看)都是原子的(要不不执行,要不执行完)。

4、内存可见性问题。

5、指令重排序问题。

解决方法:

1、针对线程的“抢占式线程”,“随即调度”。

2.代码结构上:可以不让多个线程同时修改一个变量,但这个要分情况,有时可以调整,但有时是无法实现调整的。

3、不是“原子的”操作:可以将count++生成的几个指令,通过一些方法,将其打包,使其成为一个“整体”。

“加锁”

可以通过“加锁”,来实现这样的效果。锁具有“互斥”,“排他”这样的特性。

在java中,加锁的方式有好多种,最主要使用的方式是通过加synchronized关键字,来加锁。

加锁的目的是,把count++的三个操作(load,add,save)打包成一个原子操作。

但这里进行锁操作,需要先准备一个“锁对象”,加锁,解锁的操作都是依托锁对象来执行的。

synchronized(对象){ }

进入{}后,会进行加锁(lock),出{}后,进行解锁(unlock)。

在java中,任何一个对象都可以成为锁对象。也就是说()中的内容可以是随意的,但必须为对象。

如果一个线程,针对一个对象加上锁之后,若别的线程也想对这个对象上锁,该线程就会产生阻塞(BLOCKED),直到上一个线程解锁为止,该线程才能继续操作。

解析:每次count++之前,进行上锁,count++之后,进行解锁。

若两个对象针对不同的对象加锁,则就不会有锁竞争,也不会产生阻塞。此时还是会存在线程安全问题。

这里能否通过上锁解决线程安全问题,最主要的就是是否对同一个对象上锁

synchronized是调用系统的api进行加锁的,系统的api本质上是靠cpu上的特定指令完成加锁的。

通过锁竞争,让第二个线程的指令无法插入到第一个线程的执行指令之间,而不是禁止第一个线程被调度出cpu.

若一个线程加锁,另一个线程不加锁,又会怎样呢?

通过结果可以看出,仅对一个线程加锁,是无法解决线程安全问题的。未加锁的线程中的count++操作,仍然会被另一个线程插队。

synhrionzed的别的加锁方式:

class Test{public int count1;synchronized  public void add(){count1++;}
}
public class Thread13 {//线程安全问题public static void main(String[] args) throws InterruptedException {
//        Object block = new Object();Test test = new Test();Thread t1=new Thread(()->{for(int i=1;i<=10000;i++){test.add();//将count++放到类的方法中,对该类进行加锁}});Thread t2=new Thread(()->{for(int i=1;i<=10000;i++){test.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+test.count1);}
}

或者是以这种形式:

这都是对同一个对象加锁,这等价于将锁加到方法上。类似于上面的代码

若是synchronized加到静态方法上,则相当于对类进行了加锁

后两个原因之后再解释。

关于String:

“可重入”性

若加两层锁,会怎样呢?

感觉这样写,会进入一个死等的状态,但结果:

正常输出!!!

这又是什么原因呢?

是因为加的两次锁,是在同一个线程中进行的,在第二次加锁的时候,知道该线程已经加过锁了,就不会进行阻塞,会继续执行代码,这个特性称为“可重入”

使用“可重入”性,就会避免类似上面的代码出现阻塞状态。

如果没有“可重入”性,当写的代码非常复杂时,就非常容易出现这样的阻塞状态。因为加锁的代码可能会非常隐蔽。

底层内部实现可重入行:

有一个计数器,最初为0,在第一次加锁的时候,计数器+1,同时记录是哪个线程加的锁;

在第二次加锁的时候,此时计数器为1,判定持有锁线程和加锁线程是否为同一个线程,若为同一个线程,说明该线程已经加过锁了,就不再加锁了,仅对计数器+1;若不为同一线程,则加锁线程就会进入阻塞状态。

解锁的时候,是从内层向外层以此解锁,每到 },计数器就-1,当计数器为0,就真正实现解锁了。

注意:整个过程只有一把锁,

死锁

加锁能够解决线程安全问题,但是若加锁处理不当,就可能产生死锁。

产生死锁的四个必要条件(全部具备,才会产生死锁):

1、互斥使用:一个线程获取到了这把锁,另一个线程也想获取这把锁,就进入了阻塞状态。

2、不可抢占:一个线程拿到这把锁之后,只能主动解锁,别的线程不能强行把锁抢走。

3、请求和保持:一个线程获取锁A之后,尝试获取锁B.

4、循环等待:该线程尝试获取锁,进入阻塞状态,未获取到,就一直处于阻塞状态。

死锁的三种场景:

1、一个线程,一把锁:就像上面的在一个线程内,两次获取同一把锁,若没有可重入性,则该线程就会进入死锁状态。

2、两个线程,两把锁:线程1获取到了锁A,线程2获取到了锁B;接下来,线程1尝试获取锁B,线程2尝试获取锁A。两个线程都不能获取到,都进入了阻塞状态,就产生了死锁。

3.M各线程N把锁:

最经典的问题:哲学家就餐问题:假设5个哲学家就餐,但直有5根筷子,

针对上述问题,解决死锁,有几种方法:

1、加一个筷子(加一把锁)。

2、减少一个哲学家(减少一个线程)。

3、让线程获取琐时,按规定顺序获取。(给锁编一个号,让线程从小到大获取锁)(这种方法比较常用)

4、银行家算法。(比较复杂,先不讨论)

java标准库中的线程安全类:

Java标准库中很多都是线程不安全的.这些类可能会涉及到多线程修改共享数据,⼜没有任何加锁措 施:

这些在java标准库中,都准备弃用了。

内存可见性

如果一个线程写,一个线程读,是否会引起线程安全问题呢?

在t1线程中,设置循环条件:判断flag是否被修改,在mian线程中通过控制台输入,修改flag:

输入1后,t1线程并没有按照预期结果结束执行!

说明引起了线程安全问题。

这是因为和内存可见性问题有关。

在t1线程的while循环中会执行两条核心指令:

1、load:读取内存中flag的值到cpu寄存器上。

2.字节跳转指令:将cup寄存器上的值和0进行比较。

在用户输入值之前,t1线程已经进行过多次循环了(上亿次),其中load每次从内存中读取的值都是相同的,并且load操作的开销远超过字节跳转(访问寄存器的速度远远超过访问内存)

这之后,再修改flag的值,就没有作用了。

volatile关键字

想让自己写的代码,无论什么情况都不会出现内存可见性问题,可以用volatile关键字来修饰,这样就可以使上述优化强制关闭,保证每次循环都是从内存中读取数据的。(同时,也降低了代码执行的效率)


volatile关键字的作用主要有如下两个:

保证内存可见性:基于屏障指令实现,即当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
保证有序性:禁止指令重排序。编译时 JVM 编译器遵循内存屏障的约束,运行时靠屏障指令组织指令顺序。
注意:volatile 不能保证原子性

引入volatile关键字,相当于把主动权交给了程序员自己。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64805.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI的进阶之路:从机器学习到深度学习的演变(四)

AI的进阶之路&#xff1a;从机器学习到深度学习的演变&#xff08;三&#xff09; 五、深度学习的应用领域 深度学习的应用领域广泛&#xff0c;涵盖了计算机视觉、自然语言处理、语音识别和推荐系统等多个方面。以下将详细探讨这些关键应用领域&#xff0c;展示深度学习在不同…

Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)

Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版) 文章目录 Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)一.环境准备1.服务器准备2.环境配置3.设置主机名4.修改国内镜像源地址5.配置时间同步6.配置内核转发及网桥过滤二.容器运行时Containerd安装(所有节点)…

圣诞快乐(h5 css js(圣诞树))

一&#xff0c;整体设计思路 圣诞树h5&#xff08;简易&#xff09; 1.页面布局与样式&#xff1a; 页面使用了全屏的黑色背景&#xff0c;中央显示圣诞树&#xff0c;树形由三层绿色的三角形组成&#xff0c;每一层的大小逐渐变小。树干是一个棕色的矩形&#xff0c;位于三角…

PostgreSQL和Postgis安装

Windows下PostgreSQL和对应的版本的Postgis安装 PostgreSQL安装 1、官网下载地址 https://www.enterprisedb.com/downloads/postgres-postgresql-downloads 2、根据自己的系统下载完成&#xff0c;Windows下可以直接傻瓜式安装就OK 建议不要通过自带的这个程序安装postgis,…

拒绝 Helm? 如何在 K8s 上部署 KRaft 模式 Kafka 集群?

首发&#xff1a;运维有术 今天分享的主题是&#xff1a;不使用 Helm、Operator&#xff0c;如何在 K8s 集群上手工部署一个开启 SASL 认证的 KRaft 模式的 Kafka 集群&#xff1f; 本文&#xff0c;我将为您提供一份全面的实战指南&#xff0c;逐步引导您完成以下关键任务&a…

面向微服务的Spring Cloud Gateway的集成解决方案:用户登录认证与访问控制

&#x1f3af;导读&#xff1a;本文档详细描述了一个基于Spring Cloud Gateway的微服务网关及Admin服务的实现。网关通过定义路由规则&#xff0c;利用负载均衡将请求转发至不同的后端服务&#xff0c;并集成了Token验证过滤器以确保API的安全访问&#xff0c;同时支持白名单路…

浅析InnoDB引擎架构(已完结)

大家好&#xff0c;我是此林。 今天来介绍下InnoDB底层架构。 1. 磁盘架构 我们所有的数据库文件都保存在 /var/lib/mysql目录下。 由于我这边是docker部署的mysql&#xff0c;用如下命令查看mysql数据挂载。 docker inspect mysql-master 如下图&#xff0c;目前只有一个数…

Ajax中的axios

既然提到Ajax&#xff0c;那就先来说一说什么是Ajax吧 关于Ajax Ajax的定义 Asynchronous JavaScript And XML&#xff1a;异步的JavaScript和XML。 反正就是一句话总结&#xff1a; 使用XML HttpRequest 对象与服务器进行通讯。 AJAX 是一种在无需重新加载整个网页的情况下&…

苹果手机怎么清理空间:拯救你的拥挤手机

在数字生活的海洋中&#xff0c;我们的苹果手机就像一艘小船&#xff0c;载满了照片、应用、视频和各种下载的“宝贝”。随着时间的推移&#xff0c;这艘小船开始变得拥挤&#xff0c;航行速度放缓&#xff0c;甚至有时候直接卡壳。苹果手机怎么清理空间&#xff1f;是时候学会…

三、使用langchain搭建RAG:金融问答机器人--检索增强生成

经过前面2节数据准备后&#xff0c;现在来构建检索 加载向量数据库 from langchain.vectorstores import Chroma from langchain_huggingface import HuggingFaceEmbeddings import os# 定义 Embeddings embeddings HuggingFaceEmbeddings(model_name"m3e-base")#…

C语言 函数嵌套

#include <stdio.h> void new_line() {printf("hehe\n"); } void three_line() {int i 0;for (i 0; i < 3; i){new_line;} } int main() {three_line();return 0; } 函数可以嵌套调用&#xff0c;但不能嵌套定义 链式访问 main有三个参数 //main函数的…

问题解决:发现Excel中的部分内容有问题。是否让我们尽量尝试恢复? 如果您信任此工作簿的源,请单击“是”。

在开发同步导出功能是遇到了如标题所示的问题&#xff0c;解决后遂记录下来供大家参考。 RestController public class XxxController {PostMapping("/export")public BaseResponse export(RequestBody PolicyErrorAnalysisExportReq exportReq, HttpServletRespons…

基于ST STM32MP257FAK3的MP2控制器之工业PLC 方案

简介 1.可编程逻辑控制器&#xff08;PLC&#xff09;是种专门为在工业环境下应用而设计的数字运算操作电子系统。它采用一种可编程的存储器&#xff0c;在其内部存储执行逻辑运算、顺序控制、定时、计数和算术运算等操作的指令&#xff0c;通过数字式或模拟式的输入输出来控制…

golang自定义MarshalJSON、UnmarshalJSON 原理和技巧

问题出现的原因&#xff1a;在前后端分离的项目中&#xff0c;经常出现的问题是时间戳格式的问题。 后端的日期格式兼容性强&#xff0c;比较完善。前端由于各种原因&#xff0c;日期格式不完善。 就会产生矛盾。 ms int64比较通用&#xff0c;但是unix时间没有可读性&#xff…

初始Python篇(7)—— 正则表达式

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; Python 目录 正则表达式的概念 正则表达式的组成 元字符 限定符 其他字符 正则表达式的使用 正则表达式的常见操作方法 match方法的…

使用 AI 辅助开发一个开源 IP 信息查询工具:一

本文将分享如何借助当下流行的 AI 工具,一步步完成一个开源项目的开发。 写在前面 在写代码时&#xff0c;总是会遇到一些有趣的机缘巧合。前几天&#xff0c;我在翻看自己之前的开源项目时&#xff0c;又看到了 DDNS 相关的讨论。虽然在 2021 年我写过两篇相对详细的教程&am…

Powershell学习笔记

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

《Java源力物语》-2.异常训练场

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” \quad 在java.lang古域的一处偏僻角落&#xff0c;矗立着一座古老的训练场。青灰色的围墙上布满了密密麻麻的源力符文&#xff0c;这些符文闪烁着…

一起学Git【第二节:创建版本库】

创建库 这个库相当于一个目录&#xff0c;目录中的文件都被Git管理&#xff0c;会记录每个文件的修改删除和添加工作&#xff0c;便于之后随时跟踪历史记录还原到之前的某一版本。如何创建库呢&#xff1f;有两种方式&#xff0c;本地创建库和云端克隆一个库。 1.本地创建库 …

HarmonyOS NEXT 技术实践-基于基础视觉服务的多目标识别

在智能手机、平板和其他智能设备日益智能化的今天&#xff0c;视觉识别技术成为提升用户体验和智能交互的重要手段。HarmonyOS NEXT通过基础视觉服务&#xff08;HMS Core Vision&#xff09;提供了一套强大的视觉识别功能&#xff0c;其中多目标识别作为其关键技术之一&#x…