Thread 类的基本用法

目录

什么是线程?

编写多线程程序

线程创建的方式

继承 Thread 类,重写 run 方法

实现 Runnable 接口,重写 run 方法

匿名内部类创建 Thread 子类

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

lambda表达式

Thread 类和常用方法

Thread 的常见构造方法

Thread 的常见属性

启动线程

start 和 run 的区别

中断线程

等待线程

获取当前线程引用

休眠当前线程


什么是线程?

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

相比于进程而言,线程更轻量

创建 线程 比创建 进程 更快

销毁 线程 比销毁 进程 更快

调度 线程 比调度 进程 更快

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

而在 Java 标准库中,将这些 API 进行了封装,因此我们可以直接使用 Thread 类来实现多线程程序

编写多线程程序

要编写多线程程序,就需要使用 Thread 类,我们继承 Thread 类,并重写 run 方法

class MyThread extends Thread{@Overridepublic void run() {// run 方法,线程的入口方法while (true) {System.out.println("thread");}}
}public class ThreadDemo {public static void main(String[] args) {// 根据类创建出线程实例Thread thread = new MyThread();// 调用 Thread 的 start 方法,调用系统 API 在系统内核中创建出线程thread.start();while (true) {System.out.println("main");}}
}

我们在继承 Thread 时,发现这个类可以直接使用,不需要导包,这是为什么呢?

这是因为 Java 标准库中有一个特殊的包 java.lang,而使用 java.lang 包下的所有类,都不需要手动导入, Thread 类就在这个包中,因此不需要手动导包

run 方法的作用是什么呢?

main 方法是 一个 Java 程序的入口方法,而 run 方法的作用 与 main 方法类似,是该线程的入口方法

为什么选择重写 run 方法,而不是直接使用 Thread 类的 run 方法呢?

方法重写,本质上就是为了在现有的类的基础上进行扩展,实现一个线程, 就是想让这个线程执行实现我们需求的代码,但标准库自带的 run 方法并不知道我们的需求,要实现的业务逻辑需要我们手动指定,因此,我们就需要针对原有的 Thread 进行扩展

注意:不要忘了调用 start 方法,创建出线程

观察运行结果:

我们可以发现,此时两个循环都在执行,这也可以看出,这两个线程是两个独立的执行流,它们互不干扰,各自执行各自的代码

那么,是先打印 thread,还是先打印 main 呢? 

当有多个线程的时候,这些线程执行的先后顺序是不确定的,这是因为操作系统内核中实现的线程调度顺序是 随机调度 的,随机调度,也就意味着:

一个线程,什么时候被调度到 CPU 上执行,时机是不确定的

一个线程,什么时候从 CPU 被调度走,让其他线程执行,时机也是不确定的

从打印结果我们也可以看出,每次打印的 main / thread 的个数是不确定的

由于两个线程中都在执行死循环逻辑,而循环体只是单纯的打印,因此这两个循环执行的速度非常快,也就导致 CPU 占用率比较高,也就会进一步提高电脑的功耗

我们使用 Thread 提供的静态方法 sleep 来降低循环速度,让线程每打印一次,就休眠一段时间:


class MyThread extends Thread{@Overridepublic void run() {// run 方法,线程的入口方法while (true) {System.out.println("thread");// 一次打印完成后,休眠 2sThread.sleep(2000);}}
}public class ThreadDemo {public static void main(String[] args) {// 根据类创建出线程实例Thread thread = new MyThread();// 调用 Thread 的 start 方法,调用系统 API 在系统内核中创建出线程thread.start();while (true) {System.out.println("main");// 一次打印完成后,休眠 2sThread.sleep(200);}}
}

在使用 sleep 方法时,会抛出一个 受查异常 InterruptedException

意味着在 sleep 2s 的过程中,该线程可能被提前唤醒

对异常进行处理:


class MyThread extends Thread{@Overridepublic void run() {// run 方法,线程的入口方法while (true) {System.out.println("thread");// 一次打印完成后,休眠 2stry {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class ThreadDemo {public static void main(String[] args) {// 根据类创建出线程实例Thread thread = new MyThread();// 调用 Thread 的 start 方法,调用系统 API 在系统内核中创建出线程thread.start();while (true) {System.out.println("main");// 一次打印完成后,休眠 2stry {Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

在 MyThread 中,只能通过 try catch 处理异常,而不能通过 throws 抛出异常,这是为什么呢?

这是因为 MyThread 继承自 Thread,如果加上了 throws ,就修改了方法签名(父类 Thread 的 run 方法没有 throws 这个异常)此时也就不能构成重载了, 因此,子类在重写的时候,不能通过 throws 抛出异常

此时我们再次运行程序,打印的速度就会慢很多

上述代码中,我们通过 继承 Thread,重写 run 方法的方式,实现了线程的创建

但创建线程的方式不仅仅只要这一种,接下来,我们就来继续学习其他线程创建的方式

线程创建的方式

继承 Thread 类,重写 run 方法

也就是我们上述实现的方式,通过继承 Thread 来创建一个线程类

// 继承 Thread,重写 run 方法
class MyThread1 extends Thread {@Overridepublic void run() {while (true) {System.out.println("继承 Thread 类,重写 run 方法");}}
}

创建 MyThread1 实例,并调用 start 方法启动线程

        Thread t1 = new MyThread1();t1.start();

实现 Runnable 接口,重写 run 方法

// 实现 Runnable,重写 run
class MyThread2 implements Runnable {@Overridepublic void run() {while (true) {System.out.println("实现 Runnable 接口,重写 run 方法");}}
}

Runnable 可理解为 "可执行的",通过这个接口就可以抽象表示出一段可以被其他实体来执行的代码

由于 Runnable 表达的代码只是一段可以执行的代码,因此,在创建实例时,还是需要使用 Thread 类,才能真正在系统中创建出线程

通过实现 Runnable的方式来创建线程,将 线程 和 要执行的任务 进行了解耦合

创建 Thread 类实例,并调用 start 方法启动线程

        // 创建 Thread 类实例,调用 Thread 的构造方法时将 Runnable 对象作为 target 参数Thread t2 = new Thread(new MyThread2());t2.start();

匿名内部类创建 Thread 子类

还是通过继承 Thread 的方式创建实例,但使用的是匿名内部类

        // 继承 Thread,重写 run,使用匿名内部类Thread t3 = new Thread() {@Overridepublic void run() {while (true) {System.out.println("匿名内部类创建 Thread 子类");}}};t3.start();

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

既然可以通过匿名内部类的方式创建 Thread 子类,也就可以通过匿名内部类的方式创建 Runnable 子类对象

        // 实现Runnable,重写 run,使用匿名内部类Thread t4 = new Thread(new Runnable() {@Overridepublic void run() {while (true){System.out.println("匿名内部类创建 Runnable 子类对象");}}});t4.start();

在 Thread 构造方法的参数中填写 Runnable 的匿名内部类实例 

lambda表达式

        // 使用 lambda 表达式Thread t5 = new Thread(() -> {while (true) {System.out.println("lambda表达式");}});t5.start();

通过lambda表达式 创建 Runnable 子类对象,相当于 实现 Runnable 重写 run 方法

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

每个执行流都需要一个对象来描述,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度和线程管理

接下来,我们来学习 Thread 类的重要属性和常用方法

Thread 类和常用方法

Thread 的常见构造方法

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

关于线程的名称,当我们自己创建线程,且未命名时,默认是按照 Thread-0、Thread-1、Thread-2.... 来对不同的线程命名的,且线程之间的名字是可以重复的,线程的名称对于线程的执行,没有太大的影响,对其进行命名,主要为了方便我们进行调试 

Thread 的常见属性

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

 ID:ID 是线程的唯一标识,是由 JVM 自动分配的身份标识,具有唯一性

名称:方便我们进行调试

状态:表示线程当前所处的状态(如 就绪状态 阻塞状态 等)

优先级:优先级高的线程理论上来说更容易被调度到,但在 Java中设置优先级,会对内核调度器的调度过程产生一些影响,效果不太明显(由于系统的随机调度)

守护(后台)线程:后台线程的运行,不会阻止进程结束

如何理解呢?

后台进程,也就有 前台线程

前台线程的运行,会阻止进程结束

后台线程的运行,不会阻止进程结束

我们通过一个具体的例子来进一步理解 前台线程 和 后台线程

public class ThreadDemo2 {public static void main(String[] args) {Thread thread = new Thread(() -> {while (true) {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();}
}

当 main 中执行到 thread.start() 时,main 线程已经结束了,但 t 仍然在继续执行,仍未结束,因此,此时的 thread 是一个 前台线程

我们将其设置为 后台线程 

public class ThreadDemo2 {public static void main(String[] args) {Thread thread = new Thread(() -> {while (true) {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.setDaemon(true);thread.start();}
}

注意:要在 thread.start() 方法执行之前,也就是线程创建前,进行设置 

观察运行结果: 

此时,控制台还什么都没打印,进程就结束了

我们创建的线程,默认是前台线程,会阻止进程结束,只要前台线程没执行完,进程就不会结束(即使 main 已经执行完毕) 即,JWM 会在一个进程的所有前台线程结束后,才会结束运行

是否存活:表示内核中的线程是否还存在

Java中定义的 线程对象(Thread)实例,虽然表示一个线程,但这个对象本身的生命周期,和内核中的 PCB 生命周期,是不完全一样的

我们来看具体的例子:

public class ThreadDemo3 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}});System.out.println("start 之前:" + thread.isAlive());// 在 start 之前,设置 线程为后台线程thread.setDaemon(true);thread.start();System.out.println("start 之后:" + thread.isAlive());Thread.sleep(3000);// 3s 后,线程 thread 已经结束了System.out.println("thread 结束之后" + thread.isAlive());}
}

运行结果: 

当执行完 new Thread 时,此时 thread 对象已经创建好了,但是 内核 PCB 还没有,因此,isAlive 也就是 false

而当执行到 thread.start() 时,才真正在 内核 中创建出 PCB,此时的 isAlive 就是 true

当线程的 run 方法执行完毕时,此时这个内核中的线程就结束了(内核 PCB 就释放了)

但此时 t 变量还在,因此,isAlive 也就是 false

中断:关于线程的中断,我们后续再进行详细说明

启动线程

我们通过覆写 run 方法,创建一个线程对象,但线程对象被创建出来,并不意味着线程就开始运行了

覆写 run 方法是提供给线程要做的事情的指令清单,但当调用 start 方法时,才是真的在操作系统的底层创建出一个线程

Thread 类使用 start 方法启动一个线程,对于同一个 Thread 对象来说,start 方法只能调用一次:

public class ThreadDemo4 {public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();t.start();}
}

 观察运行结果:

此时抛出 llegalThreadStateException 异常

调用 start 方法创建出新的线程,本质上是 start 调用系统的 API 来完成创建线程的操作

因此,若我们想要启动更多的线程,就需要创建新的对象

start 和 run 的区别

我们来看下面的代码:

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

运行结果:

此时控制台打印出 main 和 thread 说明,两个线程都在执行

但,当我们使用 t.run 方法时:

public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// t.start();t.run();while (true) {System.out.println("main");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

运行结果:

 

可以看到,此时控制台只会打印出 thread,这是因为,此时调用 run 方法后,仍在 main 主线程中,并没有创建出新的线程,此时代码就停留在 run 方法的循环中,而下面 main 中的循环无法执行到

从上述例子我们就可以看出:

start 方法,用于创建出一个新的线程,当线程对象被创建出来时,并不意味着线程就开始运行了,只有当调用 start 方法时,才会真正在操作系统底层创建出一个线程

而 run 方法,是线程的入口方法,我们通过覆写 run 方法,实现我们的需求,告诉线程要做什么事情

中断线程

中断一个线程,即终止一个线程,让线程的 run 方法执行完毕,那么,该如何让线程提前终止呢?

其核心也就是让 run 方法能够提前结束

public class ThreadDemo6 {public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}
}

若我们想结束上述死循环,该如何实现呢?

我们可以引入一个 标志位

public class ThreadDemo6 {private static boolean isQuit = false;public static void main(String[] args) {Thread t = new Thread(() -> {while (isQuit) {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("线程结束!");});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}isQuit = true;System.out.println("使用标志位让 t 线程结束");}
}

观察运行结果:

通过上述代码,就可以使线程结束掉,线程什么时候结束,取决于另一个线程中何时修改 isQuit 的值

当设置了 isQuit = true 后,是主线程先打印 "使用标志位让 t 线程结束",还是 t 线程先打印 "线程结束!“,是不确定的(由于线程的随机调度)

上述我们通过定义一个变量 isQuit 来实现结束线程

而 Thread 类中内置了这样的变量:

public class ThreadDemo6 {public static void main(String[] args) {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程结束!");});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// 使用 interrupt 方法修改标志位的值t.interrupt();System.out.println("使用标志位让 t 线程结束");}
}

Thread.currentThread():获取当前线程实例 t

isInterrupted():判定当前线程是否中断

t.interrupt():设置标志位,相当于之前的 isQuit = true

运行程序:

我们发现:当调用 interrupt 方法后,线程并没有结束,而是 抛出了 InterruptedException 异常,并被 catch 捕获,打印出了异常信息

这是因为在调用 interrupt 方法时, sleep 的休眠时间还没到,被提前唤醒了,此时就会抛出 InterruptedException 异常(然后就被 catch 捕获到),并且清除 Thread 对象的 isInterrupted 标志位

我们通过 interrupt 方法,将标志位设置为了 true,但由于 sleep 提前唤醒操作,此时就又把标志位设置为了 false,因此,循环还会继续执行

若我们不使用 sleep :

此时线程能够正常结束

若我们要使用 sleep 方法,且想要让线程结束,只需要在 catch 中加上 break 即可

因此,中断一个线程有两种常用的方式:

通过共享的标记来进行中断

调用 interrupt() 方法来通知

等待线程

由于线程的随机调度,抢占式执行,因此,多个线程的执行顺序是不确定的

虽然线程底层的调度是无序的,但我们可以在应用程序中,通过一些 API 来影响线程的执行顺序,其中 join 就是用来影响 线程结束 的先后顺序的方法

当我们需要一个线程等待另一个线程结束后,才能继续执行,此时就可以使用 join 等待另一个线程结束

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

使用 join 方法等待线程结束,那么,是谁等谁呢?

哪个线程中调用了 join 方法,就是该线程等待其他线程结束

public class ThreadDemo7 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("thread");try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t 线程结束");});t.start();// 等待 t 线程结束t.join();System.out.println("main 线程结束");}
}

观察运行结果:

main 线程要等到 t 线程结束后,才会继续执行打印操作

在执行 join 方法时,若 t 线程正在运行,main 线程就会阻塞(暂时不参与 CPU 执行)

而当 t 线程运行结束后,main 线程就会从阻塞中恢复过来,并继续向下执行

通过 阻塞,就使得这两个线程的结束时间产生了先后顺序

sleep 也可以用来等待,那么,什么时候使用 sleep,什么时候使用 join呢?

我们来看下面这个例子:

在主线程中创建一个新的线程,由新线程完成一系列的运算,再由主线程负责获取到最终结果 

public class ThreadDemo8 {private static int result = 0;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 1; i <= 100; i++) {result += i;}});t.start();// 此时,不知道 t 线程要执行多久,就可以使用 join 等待// join 会以 t 线程执行结束作为等待的条件// 什么时候 t 线程运行结束,join 就什么时候结束等待t.join();System.out.println("result: " + result);}
}

相比于 sleep(固定时间的等),join 则是固定任务的等,会等到其他线程任务完成后才继续往下执行

此时计算量并不大,因此运行速度很快,但若计算量很大时, t 线程进行运算,main 线程等待,此时的运算速度就会比较慢:

public class ThreadDemo8 {private static long result = 0;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (long i = 1; i <= 10_000_000L; i++) {result += i;}});long beginTime = System.currentTimeMillis();t.start();// 此时,不知道 t 线程要执行多久,就可以使用 join 等待// join 会以 t 线程执行结束作为等待的条件// 什么时候 t 线程运行结束,join 就什么时候结束等待t.join();long endTime = System.currentTimeMillis();System.out.println("result: " + result);System.out.println("time = " + (endTime - beginTime) + " ms");}
}

运行结果:

 

此时,我们可以再创建一个线程,来一起完成运算:

public class ThreadDemo9 {private static long result = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {long tmp = 0;for (long i = 1; i < 5_000_000L; i++) {tmp += i;}result += tmp;});Thread t2 = new Thread(() -> {long tmp = 0;for (long i = 5_000_000L; i <= 10_000_000L; i++) {tmp += i;}result += tmp;});long beginTime = System.currentTimeMillis();t1.start();t2.start();// 等待 t1 和 t2 线程计算完成t1.join();t2.join();long endTime = System.currentTimeMillis();System.out.println("result: " + result);System.out.println("time = " + (endTime - beginTime) + " ms");}
}

t1 线程计算前一半的结果,t2线程计算后一半的结果,main 线程等待 

运行结果: 

 

使用两个线程完成计算时,虽然时间不是使用一个线程计算时的 1/2,但也大幅度缩短了运行时间

当我们直接使用 join 时,就相当于是 死等,一定要等到其他线程执行完后才能继续向下执行,但使用 死等,很容易导致线程卡住,无法继续处理后续的逻辑

因此,我们可以为其设置一个 超时时间(等待上限时间),若等待的时间达到超时时间,就不再继续等,而是继续执行

获取当前线程引用

获取当前线程的引用,我们首先会想到使用 this

若是通过继承 Thread 的方式创建线程,则可以直接使用 this 拿到线程实例

但是,若是使用 Runnable 或 lambda 的方式创建线程,this 就不再指向 Thread 对象了,此时,就只能使用 currentThread()

我们在实现中断线程时就已经使用过了,通过 currentThread() 方法来返回当前对象的引用

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

休眠当前线程

我们在前面也使用过,通过 sleep 方法让线程进入休眠状态,但是,由于线程的调度是不可控的,因此,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的

方法说明

public static void sleep(long millis) throws InterruptedException

休眠当前线程

public static void sleep(long millis, int nanos) throws InterruptedException

休眠当前线程,但精度更高

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

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

相关文章

node.js part1

Node.js Node.js 是一个跨平台JavaScript 运行环境&#xff0c;使开发者可以搭建服务器端的 JavaScript 应用程序。作用&#xff1a;使用Node.js编写服务器端程序 编写数据接口&#xff0c;提供网页资源浏览功能等等 前端工程化&#xff1a;为后续学习Vue和React等框架做铺垫. …

基于CDIO概念的人工智能物联网系统开发与实施的人才培养研究

目录 1. 引言&#xff08;Introduction&#xff09; 2. AIoT技术及其培训特点&#xff08;The Characteristics of AIOT and Its Training&#xff09; 3. 基于CDIO概念的AIoT课程改革&#xff08;CDIO Concept-based Reform of AIOT Course&#xff09; 4. AIoT课程内容安…

Idea里配置Maven版本

一、安装Maven 1. 官网下载maven地址&#xff1a; Maven – Download Apache Maven Binary是可执行版本&#xff0c;已经编译好可以直接使用。 Source是源代码版本&#xff0c;需要自己编译成可执行软件才可使用。tar.gz和zip两种压缩格式,其实这两个压缩文件里面包含的内容是…

Verilog刷题笔记50

题目&#xff1a; Given the following state machine with 1 input and 2 outputs: 解题&#xff1a; module top_module(input in,input [9:0] state,output [9:0] next_state,output out1,output out2);assign next_state[0]~in&(state[0]|state[1]|state[2]|state[3]…

Java方法01:什么是方法

本节视频链接&#xff1a;Java方法01&#xff1a;什么是方法&#xff1f;_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV12J41137hu?p45&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 Java中的‌方法‌是一段执行特定任务的代码片段&#xff0c;‌它是程序的基本构…

C#中的S7协议

S7协议-S7COMM S7COMM 进行写 CTOP->PDU type已知枚举值 0X0E连接请求0x0d连接确认0x08断开请求0x0c断开确认0x05拒绝访问0x01加急数据0x02加急数据确认0x04用户数据0x07TPDU错误0x0f数据传输 S7Header->ROSCTR已知枚举值 0X01JOB REQUEST。主站发送请求0x02Ack。从站…

Android MediaRecorder 视频录制及报错解决

目录 一、start failed: -19 二、使用MediaRecorder录制视频 2.1 申请权限 2.2 布局文件 2.3 MediaRecordActivity 2.4 运行结果 三、拓展 3.1 录制视频模糊(解决) 3.2 阿里云OSS上传文件 3.2.1 权限(刚需) 3.2.2 安装SDK 3.2.3 使用 相关链接 一、start failed…

基于spring boot的小型诊疗预约平台的设计与开发

TOC springboot262基于spring boot的小型诊疗预约平台的设计与开发 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进…

C++之模版初阶

目录 前言 1.泛型编程 2.函数模版 2.1函数模版概念 2.2函数模版格式 2.3函数模版的原理 2.4函数模版的实例化 2.5模版参数的匹配原则 3.类模版 3.1类模版的定义格式 3.2类模版的实例化 结束语 前言 前面我们学习了C的类与对象和内存管理&#xff0c;接下来我们继续学习…

【等保测评】Mysql测评中使用的命令汇总

一、身份鉴别 a) 应对登录的用户进行身份标识和鉴别&#xff0c;身份标识具有唯一性&#xff0c;身份鉴别信息具有复杂度要求并定期更换&#xff1b; mysql -uroot -p 查看登录是否需要输入口令鉴别用户身份 select user,host from mysql.user 查看是否存在相同账户…

苹果手机怎么清理重复照片的解决方案

随着智能手机摄像头技术的飞速发展&#xff0c;我们越来越依赖iPhone来记录生活中的点点滴滴。不可避免地&#xff0c;这也导致了大量重复照片的产生&#xff0c;这些重复照片不仅占用了宝贵的存储空间&#xff0c;还使得照片库显得混乱无序。本文将介绍苹果手机怎么清理重复照…

【项目实战】C++视频共享点播系统

目录 一、项目介绍 1.1 对视频共享点播系统的认识 1.2服务端程序负责功能 1.3 服务端功能模块划分 1.4 项目界面演示 1.5预备知识 二.环境搭建 2.1 安装 Jsoncpp 库 2.1.1 使用jsoncpp 2.2 引入httplib库 2.2.1 安装Git&#xff08;如果你的系统尚未安装Git&#xf…

【算法】弗洛伊德(Floyd)算法求最短路径

目录 1.弗洛伊德&#xff08;Floyd&#xff09;算法介绍 2.弗洛伊德算法图解分析 2.1思路&#xff1a; 2.2图和矩阵的准备 2.3弗洛伊德算法的步骤&#xff1a; 2.4疑问 3.弗洛伊德算法的代码实现 3.1创建图并显示距离表与前驱表 3.2完整代码 1.弗洛伊德&#xff08;Flo…

qt笔记之qml中的TextEdit、TextInput、TextArea、TextField的区别

qt笔记之qml中的TextEdit、TextInput、TextArea、TextField的区别 code review! 文章目录 qt笔记之qml中的TextEdit、TextInput、TextArea、TextField的区别一.对比二.C环境中类似功能的控件 一.对比 TextEdit、TextInput、TextArea和TextField都是用于文本输入的组件&#…

硅谷物理服务器有哪些关键优势和特点

硅谷的物理服务器设施全球知名&#xff0c;为各类企业提供了卓越的IT基础设施支持。下面将逐一探讨硅谷物理服务器的关键优势和特点&#xff0c;rak小编为您整理发布硅谷物理服务器有哪些关键优势和特点。 1. 卓越的性能 高性能计算能力&#xff1a;硅谷的物理服务器采用最新一…

内网渗透之icmp隧道传输

原理 # 为什么要建立隧道 在实际的网络中&#xff0c;通常会通过各种边界设备软/硬件防火墙、入侵检测系统来检查对外连接的情况&#xff0c;如果发现异常&#xff0c;会对通信进行阻断。 ​ # 什么是隧道 就是一种绕过端口屏蔽的方式&#xff0c;防火墙两端的数据包通过防火墙…

算法刷题记录 八十五【图论的广度优先搜索理论基础】

前言 图论章节第2篇。 第1篇&#xff1a;记录 八十二【图论理论基础及深度优先搜索算法】&#xff1b; 本文&#xff1a;记录 八十五【图论的广度优先搜索理论基础】 一、广度优先搜索理论基础 广度优先搜索理论基础 参考链接 1.1 知识点框架 1.2 模拟广度搜索的过程 在有向…

YOLT论文精读

引言 很早之前&#xff0c;在本校老师的带领下接触到了目标检测领域。在卫星遥感图像方面有一篇经典的论文《You Only Look Twice: Rapid Multi-Scale Object Detection In Satellite Imagery》。科研小白一开始反复看了几遍也没弄懂&#xff0c;决定写博客来加深自己的理解。…

小米5c解除BL锁刷机root

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 解锁BL锁 1. 下载安装 miflash_unlock&#xff1a;https://miuiver.com/miunlock/&#xff0c;登录小米账号&#xff08;需要和解锁设备绑定的账号一致&#…

进程第二部分

1.任务&#xff1a;子进程做的事情和父进程差不多&#xff08;子承父业&#xff09; 父进程创建出子进程之后&#xff0c;子进程做的事情与父进程完全不同&#xff08;自力更生&#xff09; 2.exec: int exec l(const char *path, const char *arg, ...); int exec v(const c…