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

【多线程】Thread类及其基本用法

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

在这里插入图片描述

文章目录

  • 1. Java中多线程编程
    • 1.1 操作系统线程与Java线程
    • 1.2 简单使用多线程
      • 1.2.1 初步创建新线程代码
      • 1.2.2 理解每个线程是一个独立的执行流
  • 2. 初始Thread类
    • 2.1 Thread类的常见构造方法
    • 2.2 Thread类的常见属性方法
  • 3. Thread类的基本用法
    • 3.1 创建线程
      • 3.1.1 使用继承Thread,重写run的方式
      • 3.1.2 使用实现Runnable,重写run的方式
      • 3.1.3 继承Thread,使用匿名内部类的方式
      • 3.1.4 实现Runnable类,使用匿名内部类
      • 3.1.5 lambda表达式(最推荐使用,最简单最直观写法)
    • 3.2 线程启动 —— start()
    • 3.3 线程休眠 —— sleep()
    • 3.4 获取当前线程引用 ——currentThread()
    • 3.4 线程中断 —— interrupt()
      • 3.4.1 给线程设置一个标志位
        • (1) 设置一个标志位isQuit
        • (2) 注意isQuit的位置
        • (3) isQuit不能为局部变量的原因
      • 3.4.2 调用interrupt()方法来通知
        • (1) interrupt方法的作用
        • (2) sleep为什么要清空标志位
    • 3.5 线程等待 —— join()
      • 3.5.1 join无参数
      • 3.5.2 join有参数

1. Java中多线程编程

1.1 操作系统线程与Java线程

操作系统线程】线程是操作系统中的概念,操作系统内核实现线程这样的机制,并对用户层提供一些 API 供用户使用(例如 Linux的pthread库)
Java线程】Java 标准库中提供了一个类Thread能够表示一个线程,可以认为是对操作系统提供的 API 进行了进一步的抽象和封装,即关于线程的操作是依赖操作系统提供的API
在这里插入图片描述

1.2 简单使用多线程

1.2.1 初步创建新线程代码

class MyThread extends Thread {@Overridepublic void run() {System.out.println("hello world~");}
}public class ThreadDemo {public static void main(String[] args) {MyThread t = new MyThread();t.start();}
}

打印结果如下~
在这里插入图片描述
简单了解这个例子的执行过程,在上述代码中涉及到两个线程分别是:
1)main方法所对应的线程,也可以成为主线程(一个进程里至少得有一个线程)
2)通过t.start()创建的新线程
在这里插入图片描述
即主线程中调用t.start()创建一个新的线程,这个新线程调用t.run(),当run()执行结束后这个新线程也会随之销毁,同时main线程也执行结束了(因为t.start()是main线程中最后一个语句)

1.2.2 理解每个线程是一个独立的执行流

class MyThread extends Thread {@Overridepublic void run() {while(true) {System.out.println("hello t!");}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();while(true) {System.out.println("hello main!");}}
}

打印的结果如下:两个语句交替打印!main线程和新线程是同时在进行的,打印几个hello t!再打印几个hello main!..,尽管两个线程同时进行(通过快速调度,能够交替运行),两个线程在同一控制台打印,同一控制条必须顺序输出,所以打印会有先后,(如果是单线程,只会打印其中一个,而看不到另一个)
在这里插入图片描述
注意
交替打印一直执行,因为代码没有创建其它线程,两个死循环在同一线程,快速调度执行,所以会一直循环下去~
接下来,我们进一步了解Thread类~一起来看看吧!

2. 初始Thread类

2.1 Thread类的常见构造方法

在这里插入图片描述
(1)Thread()
创建线程对象

Thread t1 = new Thread();

(2) Thread(Runnable target)
使用Runnable对象创建线程对象

Thread t2 = new Thread(new MyRunnable());

(3) Thread(String name)
创建线程对象,并命名

Thread t3 = new Thread("我的线程");

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

Thread t4 = new Thread(new MyRunnable(),"我的线程");

在这里插入图片描述
可以通过jconsole工具,查看出java进程里面的线程详情线程名字在这里显示
(5)Thread(ThreadGroup group, Runnable target)
线程可以被用来分组管理,分好的组即为线程组(仅作了解)

2.2 Thread类的常见属性方法

在这里插入图片描述
(1)ID --> getID()
ID 是线程的唯一标识,每个线程的ID都是独一无二的

public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread( () -> {System.out.println("hello t");});t.start();//获取ID属性System.out.println(t.getId());System.out.println("hello main!");}
}

打印结果如下:
在这里插入图片描述
(2)名称 --> getName()
名称是各种调试工具用到的

public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread( () -> {System.out.println("hello t");});t.start();//获取线程的名字System.out.println(t.getName());System.out.println("hello main!");}
}

打印结果如下:
在这里插入图片描述
(3)状态 --> getState()
每个线程都有自己的状态,优先级,上下文,记账信息等(在进程与线程这期内容讲到进程的这些属性,都是线程的,只不过之前谈到的进程是属于只有一个线程的进程),状态表示线程当前所处的一个情况,下期内容会进一步说明~敬请期待!

public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread( () -> {System.out.println("hello t");});t.start();//获取线程的状态System.out.println(t.getState());System.out.println("hello main!");}
}

打印结果如下:
在这里插入图片描述
(4)优先级 --> getPriority()
优先级对于系统来说只是给出"建议",理论上优先级越高,更容易被调度到~

public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread( () -> {System.out.println("hello t");});t.start();//获取ID属性System.out.println(t.getPriority());System.out.println("hello main!");}
}

打印结果如下:
在这里插入图片描述
(5)是否为后台线程 --> isDaemon()
如果是true表示为后台线程,false表示是前台线程
后台线程后台线程不阻止java进程结束,哪怕后台线程还没执行完,java进程该结束就结束
前台线程】创建的线程默认是前台线程,可以通过setDaemon()设置成后台线程,JVM会在一个进程的所有非后台线程结束后,才会结束运行

public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread( () -> {System.out.println("hello t");});t.start();//判断线程t是否为后台线程System.out.println(t.isDaemon());System.out.println("hello main!");}
}

打印结果如下:
在这里插入图片描述
(6)是否存活 --> isAlive()
判断当前的线程是否处于活动状态,描述的是操作系统里的那个线程是否存活,线程处于正在运行或准备开始运行的状态,就认为线程是"存活"的状态
1)该线程还没调用start方法,并未创建,此时调用该线程isAlive是false
2)线程的入口方法执行完毕,此时系统中对应的线程就没了,此时调用该线程isAlive就是false
下面通过一个例子深入理解isAlive()方法判断线程是否存活的标准

public class MyThread extends Thread {@Overridepublic void run() {System.out.println("run=" + this.isAlive());}
}
public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();System.out.println("开始=" + t.isAlive());t.start();System.out.println("结束="+t.isAlive());}
}

打印结果如下:
在这里插入图片描述
注意
此处结束返回值不确定!!!

System.out.println("结束="+t.isAlive()); //该值是不确定的!!!

在上述代码结束输出true是因为t线程还未执行完毕,即输出true,如果将代码为如下形式:

public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();System.out.println("开始=" + t.isAlive());t.start();Thread.sleep(1000); //延迟1sSystem.out.println("结束="+t.isAlive());}
}

在这里插入图片描述
此时结束的返回值为false,因为t对象已经在1秒内执行完毕
(7)是否被中断 --> isInterrupted()
判断该线程是否被中断,被中断返回true,未被中断返回false(在本期内容结尾处,介绍interrupted方法,此处不作过多解释)

public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread( () -> {System.out.println("hello t");});//线程是否中断t.start();System.out.println(t.isInterrupted());System.out.println("hello main!");}
}

打印结果如下:
在这里插入图片描述

3. Thread类的基本用法

3.1 创建线程

创建线程的五种方法如下:

3.1.1 使用继承Thread,重写run的方式

class MyThread extends Thread {@Overridepublic void run() {while(true) {System.out.println("hello t!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();while(true) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

3.1.2 使用实现Runnable,重写run的方式

class MyRunnable implements Runnable {@Overridepublic void run() {while(true){System.out.println("hello t!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ThreadDemo2 {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread t = new Thread(myRunnable);t.start();while(true){System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

3.1.3 继承Thread,使用匿名内部类的方式

public class ThreadDemo3 {public static void main(String[] args) {Thread t = new Thread() {@Overridepublic void run() {while(true){System.out.println("hello t!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();while(true){System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

3.1.4 实现Runnable类,使用匿名内部类

public class ThreadDemo4 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {while(true) {System.out.println("hello t!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});t.start();while(true) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

3.1.5 lambda表达式(最推荐使用,最简单最直观写法)

public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread( () -> {while(true) {System.out.println("hello t!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();while(true) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

解释说明
(1)第一种写法使用Thread的run方法描述线程入口
(2)第二种写法使用Runnable interface来描述线程入口,使用Runnable 描述一个具体的任务
(3)第一种写法和第二种写法没有本质区别
(4)匿名内部类,匿名则是没有名字,内部类是定义在类里面的类,放到哪里就是针对哪个类创建的匿名内部类

3.2 线程启动 —— start()

调用start()方法,才是启动线程了,线程才真正独立去执行了
run方法
run()叫做入口方法,是特殊的方法,重写了父类的方法,这个run方法就会被java自动执行到,即能被自动调用到,而随便写一个方法只是一个普通方法, 没有特殊含义,需要手动去调用
start方法
start()调用操作系统的API,创建新的线程,新的线程调用run方法,即通过调用Thread类的start方法来启动一个线程
区别
(1)当一个程序调用start方法,将会创建一个新的线程去自动执行run方法中的代码,但如果未使用start方法而是直接调用run方法则是直接在当前线程中执行run方法代码而不会创建新线程
(2)当一个线程启动后,不能再调用start方法,否则会报异常IllegalThreadStateException异常
总结
run()是一个入口方法,start()会去创建一个新线程去自动行run()的代码,只有通过调用线程类的start方法才能真正达到多线程的目的

3.3 线程休眠 —— sleep()

public static void sleep(long millis) throws InterruptedException

含义】sleep方法是Thread的静态方法,参数单位是ms(回顾一下静态方法,即类方法,直接可以通过类调用,无需创建对象),因为线程的调度是不可控的,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的
作用】使当前线程休眠,进入阻塞状态,即暂停执行,如果线程在睡眠状态被中断,将会抛出InterruptedException中断异常,中断/打断即sleep睡眠过程中,还没到点就被提前唤醒了
例子】以上面例子,加上sleep方法,如下:

class MyThread extends Thread {@Overridepublic void run() {while(true) {System.out.println("hello t!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();while(true) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

打印结果如下:
在这里插入图片描述
通过结果可以看到,交替打印不是像上一个例子那么快,因为加了sleep将会暂停执行1000ms,同时可以看到,此处的交替不是严格的交替,每1000毫秒(即1秒)过后是先打印main还是先打印t是不确定的,原因是多线程在CPU上调度执行的顺序是不确定的,即是随机的,尽管线程有优先级,但对于系统只是“建议”,并不一定采取
注意在哪个线程里面调用sleep()方法就休眠哪个线程

3.4 获取当前线程引用 ——currentThread()

public static Thread currentThread();

public class ThreadDemo5 {public static void main(String[] args) {Thread t = Thread.currentThread();System.out.println(t.getName());}
}

打印结果如下:即获取了当前线程的实例,打印该线程的名字,由于并未新创建线程,当前线程为main线程,即获取到main线程实例
在这里插入图片描述

3.4 线程中断 —— interrupt()

当小丁同学去上学进入学习状态,他就会按照学校规定,按时上课,放学了才能回家,但在生活中,可能会出现,家里有一个急事的情况,必须让小丁同学回家,我可以直接给老师打电话,或者直接去学校找小丁,这就涉及该如何通知小丁回家停止当前学习的问题~接下来会介绍两种常见的方式
中断这里就是字面意思,让一个线程停止下来,线程终止,本质上来说,让一个线程终止的办法就一种,即让该线程的入口run方法执行完毕!!(让入口run方法执行完毕可以有,直接return,抛出异常等等)
目前常见的有以下两种方式:

1.给线程设定一个结束标志位,通过共享的标记来进行沟通
2.调用interrupt()方法来通知

3.4.1 给线程设置一个标志位

(1) 设置一个标志位isQuit

基于上述思路,可以手动给线程设置一个标志位isQuit
首先创建线程t,该线程的代码是死循环,导致t的入口方法永远无法结束,线程也不会结束,一直在执行如下:

public class ThreadDemo6 {public static void main(String[] args) {Thread t = new Thread(()-> {while(true) {System.out.println("hello t~");}});t.start();}
}

但是!可以通过控制循环条件,设置变量isQuit像手动开关一样控制线程的结束,当!isQuit为true时执行循环内的逻辑,否则跳出循环,执行完毕,结束线程

public class ThreadDemo6 {public static boolean isQuit = false;public static void main(String[] args) {Thread t = new Thread(()-> {while(!isQuit) {System.out.println("hello t~");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t线程终止!");});t.start();//在主线程中修改isQuittry {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}isQuit = true;}
}

打印结果如下::
在这里插入图片描述该代码执行过程为打印3次"hello t~“,!isQuit 被修改为false,t 线程中的循环终止,打印” t线程终止!"这个语句后, t 线程结束,此时main线程代码也执行完毕,main线程结束,因此整个进程也随之结束

(2) 注意isQuit的位置

为什么这里将isQuit设置为成员变量,编写在main方法外,而不写在main方法里,作为局部变量呢?
下面进行演示,将isQuit设置为局部变量,发现代码报错
在这里插入图片描述-------------------------------------------------------- 在此特别说明 --------------------------------------------------------

(3) isQuit不能为局部变量的原因

错误理解
有些同学可能认为,出现该错误是因为isQuit这个使用变量超出它的作用域,但事实并非如此!线程t是可以正常拿到main线程中的变量,同一进程的线程和线程之间共用内存地址空间,比如同一进程,创建线程1、2、3,线程1创建出来的变量在线程2、3中同样也能访问到,内存地址共用,即变量也共用
正确原因
变量捕获,lambda表达式是可以访问它外面的局部变量,但这里涉及到变量捕获这一语法规则,Java语法要求变量捕获,捕获到的变量必须是final或实际final,final指的是被final修饰的变量,实际final指的是,虽然一个变量没有用final关键字修饰,但是代码中并没有尝试过修改它,即该变量没有做出过修改,在上述代码中,在最后一行作出修改isQuit为true的操作,违背变量捕获的语法要求,因此变量捕获失败,程序编译报错
解决方式
即按照最开始的代码,将isQuit设置为成员变量,在main中访问成员变量不受变量捕获规则的限制,即不会存在上述问题

3.4.2 调用interrupt()方法来通知

要知道我们自己手动创建变量控制循环是比较麻烦的~Thread类内置了一个标志位isInterrupted(),更方便实现上述效果,isInterrupted()可以理解是t对象自带的一个结束标志位,通过 t.interrupt() 方法将t内部的标志位给设置为 true,代码如下:

public class ThreadDemo7 {public static void main(String[] args) {Thread t = new Thread(()->{//currentThread 是获取到当前线程实例//此处currentThread得到对象就是t//isInterrupted 就是t对象自带的一个标志位while(!Thread.currentThread().isInterrupted()) {System.out.println("hello t!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}//把t内部标志位给设置为truet.interrupt();}
}

打印结果如下:
在这里插入图片描述但事实上,运行结果并不是我们想要的线程中断效果,通过上述打印结果可以看到,3秒时间到,调用 t.interrupt() 方法的时候,线程t并没有真正终止,而是打印异常信息之后又继续执行!该异常信息由while循环中catch捕获并打印
在这里插入图片描述为什么会出现这样的情况呢??这就需要我们了解interrupt方法的两个作用!它会将sleep提前唤醒!这正是上述代码异常的原因

(1) interrupt方法的作用

(1) 设置标志位为true
(2) 如果该线程正在阻塞中,比如正在执行的sleep、wait、join等,此时就会把该阻塞状态唤醒,通过抛出异常的方式让其立即结束

解释说明
在上述情况中,如果sleep被提前唤醒的时候,sleep会自动把isInterrupted标志位给清空(true变为false),这就导致下次再判断循环条件,循环条件还成立,循环仍然可以继续执行,而interrupt()执行时,如果t线程正在sleep,interrupt()在将标志位设置为true后,会直接将sleep强行唤醒。sleep的时间已经占据了整个循环体的绝大部分!!!非常非常多部分,因此当interrupt()执行时,几乎一定会遇到正在sleep的情况,sleep第一次执行,清空标志位,并抛出异常,这次设置的中断就翻篇,sleep第二次执行,没有这个中断的标志位了,而如果设置interrupt的时候恰好sleep唤醒,这时候是非常巧的!!概率极其低,此时执行到下一轮的循环条件就直接结束了
在这里插入图片描述

打个比方,这就好比你今天打算7点起来,但是你的妈妈五点来你的房间喊醒你,还把你房间的灯打开了,你一看手机发现才凌晨五点,又把灯关了,继续睡觉了~

主线程只调用一次interrupt(),即主线程并非是循环反复设置t内部的标志位,而是只执行一次,因此抛出1次异常后,就不会再次抛出异常
如果需要结束循环,需要怎么做呢?
解决方案在catch{}中,加一个break!
在这里插入图片描述

(2) sleep为什么要清空标志位

目的让线程自身能够对于线程何时结束有一个更明确的控制
当前interrupt方法的效果不是让线程立即结束,而是告诉它,你该结束了,它是否真的要结束,立即结束还是过一会结束,都是由代码灵活控制的!
interrupt方法只是通知,而不是"命令"!!!

比如我在学习,我的妈妈让我去超市买点菜
(1)直接无视她的要求,继续学习(不去)
(2)立即放下学习,赶紧起身去买菜(立马去)
(3)我说,等我学完这一节课我再去(等会去)

在这里插入图片描述
为什么Java这里不强制设置为命令结束操作,即一调用interrupt不是通知,而是命令,就立即结束了
原因
这种设定非常不友好,线程t何时结束,一定是线程t自己最清楚,交给t自身来决定比较好!

3.5 线程等待 —— join()

线程之间是并发执行的,即操作系统对于线程的调度是无序的,无法判定两个线程谁先执行结束,谁后执行结束!

public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()-> {System.out.println("hello t");});t.start();System.out.println("hello main");}
}

本次运行该代码打印结果如下:
在这里插入图片描述
先输出hello main还是hello t???这是无法确定的!这个代码实际执行的时候,大部分情况下都是先出hello main,比如上述打印结果,因为线程创建也有开销,但是不排除特定情况下,主线程hello main没有立即执行到的~
要知道程序猿不喜欢不确定的~有时候就需要明确规定线程的结束顺序,可以使用线程等待来实现!
等待线程就是控制两个线程结束的顺序,接下来join方法闪亮登场!

3.5.1 join无参数

public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()-> {System.out.println("hello t");});t.start();t.join();System.out.println("hello main");}
}

打印结果如下:
在这里插入图片描述
在t.join执行的时候,如果t线程还没有结束,main线程就会阻塞等待!
在这里插入图片描述
如果是t1线程中,调用t2.join就是让t1线程等待t2线程结束,t1线程阻塞,其它线程正常调度!
在哪个线程调用该线程的join方法,就是让哪个线程等待该线程结束
t.join作用
(1)main线程调用t.join的时候,如果t还在运行此时main线程阻塞,直到t线程结束,即t的run方法执行完毕,main才从阻塞中解除,才继续执行
(2)main线程调用t.join的时候,如果t已经结束了,此时join不会阻塞,就会立即执行下去
总之,t.join都能保证t线程是先结束的那个,明确控制线程结束的执行顺序!

3.5.2 join有参数

但是如果t线程里面有死循环,那main线程就要一直等待下去吗???
这里就介绍join的另一个版本,带参数的join,可以填一个参数作为"超时时间",即等待的最大时间~
join的无参版本,效果是"死等",必须等到该线程结束
join的有参版本,效果是指定最大时间,如果等待时间到了上限,还没等到,就不等了(在生活中也是如此~所以这是很常见的设定)

public class ThreadDemo1{public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()-> {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();t.join(3000);System.out.println("hello main");}
}

打印结果如下:
在这里插入图片描述
因为t线程中代码是死循环,将会一直打印hello t,使用带参数的join方法,则超过3秒后(这里举例子,实际最大等待时间可能更长),main线程执行,打印hello main,只打印一次,接着将一直循环打印hello t~这就是带参数的join方法!
本期内容就到这里结束啦~全是干货,疯狂进行知识输入!!!嘿嘿,一起加油努力吧
💛💛💛本期内容回顾💛💛💛
在这里插入图片描述
✨✨✨本期内容就到这里结束啦!继续努力吧~

相关文章:

【多线程】Thread类及其基本用法

🥰🥰🥰来都来了,不妨点个关注叭! 👉博客主页:欢迎各位大佬!👈 文章目录 1. Java中多线程编程1.1 操作系统线程与Java线程1.2 简单使用多线程1.2.1 初步创建新线程代码1.2.2 理解每个…...

Springboot 整合 Flowable(一):使用 flowable-UI 绘制流程图

目录 一、Flowable简介 二、Flowable 与 Activiti 的区别 三、流程图的绘制(以员工请假流程图为例) 1、下载 flowable 的压缩包: 2、启动包中的 tomcat 3、登录页面 4、绘制结束,导出 bpmn20.xml文件 一、Flowable简介 Fl…...

课设--学生成绩管理系统(一)

欢迎来到 Papicatch的博客 文章目录 🍉技术核心 🍉引言 🍈标识 🍈背景 🍈项目概述 🍈 文档概述 🍉可行性分析的前提 🍈项目的要求 🍈项目的目标 🍈…...

thinkphp5模型的高级应用

ThinkPHP5 是一个基于 PHP 的轻量级框架,它提供了许多便利的功能来简化 Web 开发。在 ThinkPHP5 中,模型(Model)是 MVC(Model-View-Controller)架构中的重要组成部分,负责处理数据逻辑。以下是一…...

XML XSLT:技术与应用解析

XML XSLT:技术与应用解析 XML(可扩展标记语言)和XSLT(XML样式表转换语言)是现代信息技术中不可或缺的工具。本文将深入探讨XML和XSLT的概念、技术细节以及它们在实际应用中的作用。 XML简介 XML是一种用于存储和传输…...

嵌入式单片机中项目在线仿真工具分享

前段时间,无意间发现了一个不错的在线仿真工具(Wokwi),支持多种平台,支持市面上主流的开发板,比如:STM32、ESP32、Arduino、树莓派等。 还支持常见的传感器、显示器件(LCD、LED屏幕)等,还可以播放音乐、联网、逻辑分析仪等,关键还提供了很多实际项目的案例。 这款工…...

Unity动态添加聊天文本

1.创建一个滚动视图 2.调整滚动视图的位置并删掉这个 3.创建一个输入框和一个按钮 这里插一句一定要给content添加这个组件并设置单元格大小 4创建一个脚本并编写下面代码 using System.Collections; using System.Collections.Generic; using TMPro; using Unity.VisualScrip…...

力扣-2269. 找到一个数字的 K 美丽值

文章目录 力扣题目代码工程C实现python实现 力扣题目 一个整数 num 的 k 美丽值定义为 num 中符合以下条件的 子字符串 数目: 子字符串长度为 k 。 子字符串能整除 num 。 给你整数 num 和 k ,请你返回 num 的 k 美丽值。 注意: 允许有 前…...

一个在C#中集成Python的例子

一个在C#中集成Python的例子。在C#中可以执行Python脚本,在Python中也可以调用C#宿主中的功能(clr.AddReference(Business))。 文件说明 Debug为执行目录 Mgr.exe为执行文件 Py\init.py为python初始化脚本 Py\Lib.zip为python需要的模块&…...

基于RandLA-Net深度学习模型的激光点云语义分割

一、场景要素语义分割部分的文献阅读笔记 RandLA-Net是一种高效、轻量级的神经网络,其可直接逐点推理大规模点云的语义标签。RandLA-Net基于随机点采样获得了显著的计算和内存效率,并采用新的局部特征聚合模块有效地保留了几何细节,弥补了随机…...

C语言的结构体与联合体

引言 C语言提供了结构体和联合体两种聚合数据类型,使得程序员可以创建包括多个数据类型的复杂数据结构。结构体用于将不同类型的数据组合成一个单元,而联合体用于在同一存储空间中存储不同类型的数据。本篇文章将详细介绍C语言中的结构体和联合体&#x…...

React Hooks小记(三)_forwardRef

forwardRef 【写在前面】 ​ 1、ref 的作用是获取实例,但由于函数组件不存在实例,因此无法通过 ref 获取函数组件的实例引用,而 React.forwardRef 就是用来解决这个问题的。 ​ 2、React.forwardRef 会创建一个 React 组件,这个组…...

面试复习记录

六级终于结束了,之前背的八股几乎也忘得差不多了,今天开始继续准备秋招,以下是每天的安排,会按时更新,就当是一种对自己的督促,也欢迎小伙伴们一起来互相监督。 2024.6.16 力扣:sql基础题库50…...

块级元素与行内元素详解

在网页设计与开发中,元素根据其在页面布局中的表现可分为两大类:块级元素(Block-level Elements)和行内元素(Inline Elements)。理解它们的特性和使用规则对于构建结构清晰、布局合理的网页至关重要。 块级…...

Kotlin编程实践-【Java如何调用Kotlin中带默认值参数的函数】

问题 如果你有一个带有默认参数值的 Kotlin 函数,如何从 Java 调用它而无须为每个参数显式指定值? 方案 为函数添加注解JvmOverloads。 也就是为Java添加重载方法,这样Java调用Kotlin的方法时就不用传递全部的参数了。 示例 在 Kotlin …...

中国城市统计年鉴(1985-2023年)

数据年限:1985-2023 数据格式:pdf、excel 数据内容:共分四个部分 第一部分是全国城市行政区划,列有不同区域、不同级别的城市分布情况; 第二、三部分分别是地级以上城市统计资料和县级城市统计资料,具体包括…...

RestTemplate远程请求的艺术

1 简说 编程是一门艺术,追求优雅的代码就像追求优美的音乐。 很多有多年工作经验的开发者,在使用RestTemplate之前常常使用HttpClient,然而接触了RestTemplate之后,却愿意放弃多年相处的“老朋友”,转向RestTemplate。那么一定是RestTemplate有它的魅力,有它的艺术风范。…...

Spring 整合 MyBatis 底层源码解析

大家好,我是柳岸花开。今天我们要讲的是 Spring 整合 MyBatis 的底层源码解析。希望大家能更深入理解 Spring 和 MyBatis 的整合原理,并应用到实际项目中。 由很多框架都需要和Spring进行整合,而整合的核心思想就是把其他框架所产生的对象放到…...

LeetCode 189.轮转数组

1.这个题我用的方法比较巧妙&#xff0c;大家如果觉得好的话&#xff0c;就给个免费的赞吧^ _ ^,谢谢了。 void reverse(int* nums,int left,int right) {while(left < right){int a nums[left];nums[left] nums[right];nums[right] a;left;right--;} } void rotate(int…...

JDK17 你的下一个白月光

JDK版本升级的非常快&#xff0c;现在已经到JDK20了。JDK版本虽多&#xff0c;但应用最广泛的还得是JDK8&#xff0c;正所谓“他发任他发&#xff0c;我用Java8”。 但实际情况却不是这样&#xff0c;越来越多的java工程师拥抱 JDK17&#xff0c;于是了解了一下 JDK17新语法&a…...

springboot优雅shutdown时如何保障异步线程的安全

我前面写了一篇springboot优雅shutdown的文章&#xff0c;看起来一切很美好。 https://blog.csdn.net/chenshm/article/details/139640775 那是因为没有进行多线程测试。如果一个请求中包括阻塞线程&#xff08;主线程&#xff09;和非阻塞线程&#xff08;异步线程&#xff09…...

C++格式化库fmt使用方法

1. 格式化库fmt简介 fmt github地址 api说明 格式化参数说明 内容的格式化&#xff0c;体现在代码中主要表现为字符串、基本类型、自定义类型的拼接。例如说打印日志、拼接变量等。C中我们会经常使用类似printf,snprintf(C风格使用不方便),std::string.append(繁琐), std::io…...

HTML 颜色名:网页设计的调色板

HTML 颜色名:网页设计的调色板 在网页设计和开发中,颜色是一个关键元素,它不仅影响视觉效果,还能传达情感和品牌信息。HTML 颜色名是用于在 HTML 和 CSS 代码中指定颜色的预定义名称。这些颜色名易于记忆,方便设计师和开发者快速选择和应用颜色。本文将详细介绍 HTML 颜色…...

12306 火车票价格解析 (PHP 解析)

1. 从接口拿数据 日期 出发站 终点站 都填上 xxx/otn/leftTicketPrice/queryAllPublicPrice?leftTicketDTO.train_date2024-06-15&leftTicketDTO.from_stationBJP&leftTicketDTO.to_stationSJP&purpose_codesADULT 返回的数据是这样的 {"validateMess…...

了解统计学中不同类型的分布

目录 一、说明 二、均匀分布&#xff1a; 三、机器学习和数据科学中的均匀分布示例&#xff1a; 3.1 对数正态分布&#xff1a; 3.2 机器学习和数据科学中的对数正态分布示例&#xff1a; 四、 帕累托分布 4.1 什么是幂律&#xff1f; 4.2 机器学习和数据科学中的帕累托分布示例…...

k8s-CCE创建工作负载变量引用

CCE创建工作负载变量引用 背景&#xff0c;看到cce创建负载时会生成变量&#xff0c;如下。在skywaking-agent的使用&#xff0c;想要调用cce负载变量生成service_name。 -Dskywalking.agent.authentication里含有敏感信息需要写到配置项。简单粗糙的都写到配置项好像不合适。…...

后端主流框架--Spring02

前言:上篇关于Spring的文章介绍了一些Spring的基本知识&#xff0c;此篇文章主要分享一下如何配置Spring环境&#xff0c;如何注入等。 Spring项目构建 导入Spring相关JAR包 <dependency><groupId>org.springframework</groupId><artifactId>spring…...

[数据集][目标检测]减速带检测数据集VOC+YOLO格式5400张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;5400 标注数量(xml文件个数)&#xff1a;5400 标注数量(txt文件个数)&#xff1a;5400 标注…...

分析Linux操作指令及使用场景与频率分析 持续更新

本篇主要针对在日常工作与学习中使用较多的linux指令的使用方法以及使用频次进行分析与讲解&#xff0c;旨在能够更好的掌握这些必备的技能。 linux指令非常的多&#xff0c;如果要记住所有的指令使用方法是非常困难的且要花费很长的时间&#xff0c;很多人习惯离开使用去通篇…...

Redis 字符串(String)

Redis 字符串(String) 介绍 Redis是一种开源的、高性能的键值数据库,它支持多种类型的数据结构,其中字符串(String)是Redis中最基本的数据类型之一。字符串类型可以存储任何形式的字符串,包括文本、序列化的对象或二进制数据。在Redis中,字符串类型的最大容量为512MB。 …...