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.rhkb.cn/news/494061.html

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

相关文章

服务器数据恢复—V7000存储中多块磁盘出现故障导致业务中断的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台V7000存储上共12块SAS机械硬盘&#xff08;其中1块是热备盘&#xff09;&#xff0c;组建了2组Mdisk&#xff0c;创建了一个pool。挂载在小型机上作为逻辑盘使用&#xff0c;小型机上安装的AIXSybase。 服务器存储故障&#xff1a; V7…

2024年图像处理、多媒体技术与机器学习

重要信息 官网&#xff1a;www.ipmml.org 时间&#xff1a;2024年12月27-29日 地点&#xff1a;中国-大理 简介 2024年图像处理、多媒体技术与机器学习&#xff08;CIPMT 2024&#xff09;将于2024年12月27-29日于中国大理召开。将围绕图像处理与多媒体技术、机器学习等在…

linux----文件访问(c语言)

linux文件访问相关函数 打开文件函数 - open 函数原型&#xff1a;int open(const char *pathname, int flags, mode_t mode);参数说明&#xff1a; pathname&#xff1a;这是要打开的文件的路径名&#xff0c;可以是绝对路径或者相对路径。例如&#xff0c;"/home/user/…

Redis 集群实操:强大的数据“分身术”

目录 Redis Cluster集群模式 1、介绍 2、架构设计 3、集群模式实操 4、故障转移 5、常用命令 Redis Cluster集群模式 1、介绍 redis3.0版本推出的Redis Cluster 集群模式&#xff0c;每个节点都可以保存数据和整个集群状态&#xff0c;每个节点都和其他所有节点连接。Cl…

探索 Seaborn Palette 的奥秘:为数据可视化增色添彩

一、引言 在数据科学的世界里&#xff0c;视觉传达是不可或缺的一环。一个好的数据可视化不仅能传递信息&#xff0c;还能引发共鸣。Seaborn 是 Python 中一款广受欢迎的可视化库&#xff0c;而它的调色板&#xff08;palette&#xff09;功能&#xff0c;则为我们提供了调配绚…

多模态医学图像融合概述

Part1: Summary 实际应用中&#xff0c;我们常用到多种来源不同数据进行融合&#xff1b; 包括图像分解、重建、融合规则及质量评估。如&#xff1a;MRI、CT、PET、SPECT、US等成像方式; 也可能有点云数据融合 CT&#xff0c;或MR 进行定位等&#xff1b; Part2: 融合数据的…

STM32F103 | Embedded IDE03 - 使用OpenOCD在STM32F103项目时出现下载固件失败

导言 在上一篇备忘录介绍使用OpenOCD的stlink-v2.cfg接口下载固件&#xff0c;在STM32F407的项目上很顺利。但是&#xff0c;在stm32f103上会出现下载失败。 在网上搜了一下&#xff0c;这位博主的文章解决了这个问题: https://www.iotword.com/26738.html 一、修改stm32f1x.c…

Android Studio IDE环境配置

​需要安装哪些东西&#xff1a; Java jdk Java Downloads | OracleAndroid Studio 下载 Android Studio 和应用工具 - Android 开发者 | Android DevelopersAndroid Sdk 现在的Android Studio版本安装时会自动安装&#xff0c;需要注意下安装的路径Android Studio插件…

人工智能ACA(四)--机器学习基础

零、参考资料 一篇文章完全搞懂正则化&#xff08;Regularization&#xff09;-CSDN博客 一、 机器学习概述 0. 机器学习的层次结构 学习范式&#xff08;最高层&#xff09; 怎么学 监督学习 无监督学习 半监督学习 强化学习 学习任务&#xff08;中间层&#xff0…

Qt之QML应用程序开发:给应用程序添加图标文件

开发环境: 1、Qt Creator 14.0.1 2、windows10 先看下面的步骤,不明白再返回来看下面官方指导链接。 先看下面的步骤,不明白再返回来看下面官方指导链接。 先看下面的步骤,不明白再返回来看下面官方指导链接。 --------------------------------------------------------…

代码随想录D24-25 回溯算法03-04 Python

目录 93. 复原 IP 地址 78. 子集 子集问题 90. 子集 II 491. 非递减子序列 46. 全排列 排列问题 47. 全排列 II 332. 重新安排行程 利用字典实现图 51. N 皇后 多维问题入门 37. 解数独 93. 复原 IP 地址 要点&#xff1a; 本质上和上一期的回文字串切分是相似的&am…

新能源汽车锂离子电池各参数的时间序列关系

Hi&#xff0c;大家好&#xff0c;我是半亩花海。为了进一步开展新能源汽车锂离子电池的相关研究&#xff0c;本文主要汇总并介绍了电动汽车的锂离子电池的各项参数&#xff0c;通过 MATLAB 软件对 Oxford Dataset 的相关数据集进行数据处理与分析&#xff0c;进一步研究各项参…

鸿蒙学习笔记:用户登录界面

文章目录 1. 提出任务2. 完成任务2.1 创建鸿蒙项目2.2 准备图片资源2.3 编写首页代码2.4 启动应用 3. 实战小结 1. 提出任务 本次任务聚焦于运用 ArkUI 打造用户登录界面。需呈现特定元素&#xff1a;一张图片增添视觉感&#xff0c;两个分别用于账号与密码的文本输入框&#…

左神算法基础巩固--1

文章目录 时间复杂度常数时间的操作时间复杂度的定义时间复杂度的作用剖析递归行为和递归行为时间复杂度的估算 排序选择排序冒泡排序插入排序归并排序小和问题问题描述解题思路 快速排序荷兰国旗问题问题描述 堆排序堆结构大根堆小根堆 桶排序 二分二分搜索 ^的运用不用额外空…

ROS1安装教程

一、环境准备 操作系统&#xff1a;Ubuntu 20.04 LTS 注&#xff1a;为保证系统干净&#xff0c;本文使用Docker进行 演示&#xff0c;如已安装相应的Ubuntu系统请忽略。 Docker安装Ubuntu系统步骤如下&#xff1a; # 拉取镜像 docker pull ubuntu:20.04# 创建容器 docker ru…

秒优科技-供应链管理系统 login/doAction SQL注入漏洞复现

0x01 产品简介 秒优科技提供的供应链管理系统,即秒优SCM服装供应链管理系统,是一款专为服装电商企业设计的全方位解决方案。是集款式研发、订单管理、物料管理、生产管理、工艺管理、收发货管理、账单管理、报表管理于一体的服装电商供应链管理解决方案。它涵盖了从企划到开…

【论文复刻】新型基础设施建设是否促进了绿色技术创新的“量质齐升”—来自国家智慧城市试点的证据(C刊《中国人口·资源与环境》

一、数据来源&#xff1a;住建部、国家知识产权局、中国城市统计年鉴&#xff0c;内含原始数据、处理代码和基准回归 二、数据范围&#xff1a; DID 为了延长政策效应估计的时间区间&#xff0c;将住建部公布的首批国家智慧城市作为处理组&#xff0c;非试点城市作为对照组。将…

【机器学习与数据挖掘实战】案例04:基于K-Means算法的信用卡高风险客户识别

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…

Oracle中间件 SOA之 OSB 12C服务器环境搭建

环境信息 服务器基本信息 如下表&#xff0c;本次安装总共使用1台服务器&#xff0c;具体信息如下&#xff1a; App1服务器 归类 APP服务器 Ip Address 172.xx.30.xx HostName appdev01. xxxxx.com Alias appdev01 OSB1服务器 归类 OSB服务器 Ip Address 172.xx3…

Elasticsearch-分词器详解

什么是分词器 1、分词器介绍 对文本进行分析处理的一种手段&#xff0c;基本处理逻辑为按照预先制定的分词规则&#xff0c;把原始文档分割成若干更小粒度的词项&#xff0c;粒度大小取决于分词器规则。 常用的中文分词器有ik按照切词的粒度粗细又分为:ik_max_word和ik_smart&…