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

【多线程】Thread 类 详解

Thread 类 详解

  • 一. 创建线程
    • 1. 继承 Thread 类
    • 2. 实现 Runnable 接口
    • 3. 其他变形
    • 4. 多线程的优势-增加运行速度
  • 二. Thread 类
    • 1. 构造方法
    • 2. 常见属性
    • 3. 启动线程-start()
    • 4. 中断线程-interrupt()
    • 5. 线程等待-join()
    • 6. 线程休眠-sleep()
    • 7. 获取当前线程引用
  • 三. 线程的状态
    • 1. 观察线程的所有状态
    • 2. 线程状态和状态转移的意义
    • 3. 观察线程的状态和转移

一. 创建线程

1. 继承 Thread 类

  1. 继承 Thread 来创建一个线程类,并重写 run() 方法
class MyThread extends Thread {@Overridepublic void run() {System.out.println("这里是线程运行的代码");}
}
  1. 创建 MyThread 类的实例
MyThread t = new MyThread();
  1. 调用 start 方法启动线程
t.start(); // 线程开始运行

注意:只有调用 start 函数才真正的创建了一个线程。

2. 实现 Runnable 接口

更推荐使用这种方法, 因为 Runnable 只是描述了一个任务,至于任务通过进程、线程、线程池还是什么来执行的,Runnable 并不关心,使代码更好的解耦合。

  1. 实现 Runnable 接口
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这里是线程运行的代码");}
}
  1. 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Thread t = new Thread(new MyRunnable());
  1. 调用 start 方法
t.start(); // 线程开始运行

对比上面两种方法:

  • 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
  • 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread() 来获取当前线程引用。

3. 其他变形

  • 匿名内部类创建 Thread 子类对象
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {@Overridepublic void run() {System.out.println("使用匿名类创建 Thread 子类对象");}
};
  • 匿名内部类创建 Runnable 子类对象
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使用匿名类创建 Runnable 子类对象");}
});
  • lambda 表达式创建 Runnable 子类对象
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {System.out.println("使用匿名类创建 Thread 子类对象");
});

4. 多线程的优势-增加运行速度

可以观察多线程在一些场合下是可以提高程序的整体运行效率的。

  • 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
  • serial 串行的完成一系列运算. concurrency 使用两个线程并行的完成同样的运算.
class ThreadAdvantage {// 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的private static final long count = 10_0000_0000;public static void main(String[] args) throws InterruptedException {// 使用并发方式concurrency();// 使用串行方式serial();}private static void concurrency() throws InterruptedException {long begin = System.nanoTime();// 利用一个线程计算 a 的值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {int a = 0;for (long i = 0; i < count; i++) {a--;}}});thread.start();// 主线程内计算 b 的值int b = 0;for (long i = 0; i < count; i++) {b--;}// 等待 thread 线程运行结束thread.join();// 统计耗时long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("并发: %f 毫秒%n", ms);}private static void serial() {// 全部在主线程内计算 a、b 的值long begin = System.nanoTime();int a = 0;for (long i = 0; i < count; i++) {a--;}int b = 0;for (long i = 0; i < count; i++) {b--;}long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("串行: %f 毫秒%n", ms);}
}

结果:
在这里插入图片描述

不是使用了多线程,速度一定提高

  • 多线程更适合于 CPU 密集型的程序,程序需要进行大量计算,使用多线程就可以充分利用 CPU 多核资源,如果计算量不大,主要时间用在创建线程上就得不偿失了。
  • 一个进程的多个线程共享同一份资源,资源是有限的,线程变多,竞争进一步加剧,如果时间很多浪费在竞争资源上,有可能拖慢整体的速度。
  • 多个线程之间到底是并发执行,还是并行执行是不确定的,只有真正的并行执行时,效率才有显著提升。

二. Thread 类

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

1. 构造方法

在这里插入图片描述

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2. 常见属性

在这里插入图片描述

  • ID: 线程的唯一标识,不同线程不会重复
  • 名称: 是各种调试工具用到
  • 状态: 表示线程当前所处的一个情况,下面将会进一步说明
  • 优先级: 优先级高的线程理论上来说更容易被调度到
  • 后台线程: 关于后台线程需要记住:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活: 即简单的理解,为 run 方法是否运行结束了
  • 线程的中断问题: 下面将进一步说明
    public static void main(String[] args) {// 创建线程Thread thread = new Thread(() -> {for (int i = 0; i < 5; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还活着");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});// (从 main 线程)获取 thread 线程的参数System.out.println("thread: ID: " + thread.getId());System.out.println("thread: 名称: " + thread.getName());System.out.println("thread: 状态: " + thread.getState());System.out.println("thread: 优先级: " + thread.getPriority());System.out.println("thread: 后台线程: " + thread.isDaemon());System.out.println("thread: 活着: " + thread.isAlive());System.out.println("thread: 被中断: " + thread.isInterrupted());// 启动 thread 线程thread.start();// 循环一直空转,至 thread 线程死亡while (thread.isAlive()) {}// 判断 thread 线程是否还活着System.out.println("thread: 状态: " + thread.getState());}

输出:
在这里插入图片描述

是否为后台线程:

  1. 若是后台线程,不影响程序的退出,前台线程才会影响程序的退出。
    (前台线程通常执行一些重要的任务,而后台线程用于执行一些辅助性的或者周期性的任务,以支持前台线程的工作。)

  2. 我们创建的线程默认是前台进程,所以即使 main 线程执行完毕了,进程也不能退出,需要等待我们创建的线程执行完毕。
    假如我们创建的是后台线程,那么 main 线程执行完毕后,进程直接退出,我们创建的线程即使没有执行完也被强制终止。

是否存活:

  • 调用 start() 之前,run() 方法执行完之后为 isAlive 为 false
  • 调用 start() 之后,run() 方法执行完之前为 isAlive 为 false

注意:Thread t 的生命周期和内核中线程的生命周期并不完全一致:
创建 t 对象后,调用 start() 之前,系统中没有对应的线程,
run() 执行完,系统中的线程销毁了,但 t 对象可能还在。

3. 启动线程-start()

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。

  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了。

调用 start 方法, 才真的在操作系统的底层创建出一个线程。
注意:一个线程只能 start 一次

start() 和 run() 的区别

  1. run 单纯的只是一个普通方法,描述了任务的内容,
    start 则是一个特殊的方法,内部会在系统中创建一个线程。
  2. run 只是一个普通的方法,在 main 线程里面调用 run, 并不会创建线程,只是在 main 线程中执行 run 中的代码,既然 run 和其他代码一样都是 在 main 线程中执行的,那么就得按照从前到后的顺序执行代码。

4. 中断线程-interrupt()

注意:中断一个线程,不是让线程立即就停止,只是通知该线程说你该停止了,具体是否真的停止,取决于线程里面代码的写法。(与操作系统里面的中断不一样)

线程一旦进到工作状态,他就会按照行动指南上的步骤(run 方法)去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如张三(主线程)让 李四 (新创建的线程)进行转账业务,李四正在工作时,老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。

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

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

示例-1: 使用自定义的变量来作为标志位.
因为多个线程共用一块空间,所以 多个线程修改的标志位是同一个。
注意需要给标志位上加 volatile 关键字,防止编译器优化为不访问内存,从而感知不到了标志位的变化。

class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了大事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");target.isQuit = true;}
}

输出:
在这里插入图片描述

使用共享的标记来进行沟通的话,线程收到通知不及时,比如说线程在休眠,需要等到休眠结束后才能收到通知。

示例-2: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.
(Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记. )

在这里插入图片描述

  • 使用 thread 对象的 interrupt() 方法通知线程结束.
    (interrupt() 方法只是设置了一个标志位,具体要不要中断,还要被中断线程的配合,还是取决于被中断线程决定是否中断。)
class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {// 两种方法均可以while (!Thread.interrupted()) {
//                while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName()+ ": 有内鬼,终止交易!");// 注意此处的 breakbreak;}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了大事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");thread.interrupt();}
}

thread 收到通知的方式有两种:

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志,也就是说 中断标志位为 false
  • 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.
  1. 否则,只是内部的一个中断标志被设置,thread 可以通过
  • Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
  • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

使用 interrupt() 这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到,但是如果是自己使用的标志位的话,线程不能及时收到中断通知。

示例-3: 观察标志位是否清除

标志位是否清除, 就类似于一个开关.

  • Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 “清除标志位”
  • Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为 “不清除标志位”.

使用 Thread.isInterrupted() , 线程中断会清除标志位.

在这里插入图片描述

观察源码可以发现, Thread.isInterrupted() 内部实际上是调用了 Thread.currentThread().isInterrupted() 只不过参数传了一个 true, 代表清除标志位。

class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.interrupted());}}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");thread.start();thread.interrupt();}
}

在这里插入图片描述
使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除.

class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().isInterrupted());}}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");thread.start();thread.interrupt();}
}

在这里插入图片描述

为什么一个清除,一个不清除?它们都用于什么场景 ?

Thread.interrupted() 清除标志位是为了下次继续检测标志位。如果一个线程被设置中断标志后,选择结束线程那么自然不存在下次的问题,而如果一个线程被设置中断标识后,进行了一些处理后选择继续进行任务,而且这个任务也是需要被中断的,那么当然需要清除标志位了。重新设置为 false, 这样就可以下次继续检测标志位。

5. 线程等待-join()

线程等待主要是为了控制线程结束的先后顺序。
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。

在这里插入图片描述

class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable target = () -> {for (int i = 0; i < 4; i++) {try {System.out.println(Thread.currentThread().getName()+ ": 我还在工作!");Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我结束了!");};Thread thread1 = new Thread(target, "李四");Thread thread2 = new Thread(target, "王五");System.out.println("先让李四开始工作");thread1.start();// 这行代码是在 main 线程中调用的,所以是 main 线程等待 thread1 线程结束,main 线程才能继续往下执行thread1.join();System.out.println("李四工作结束了,让王五开始工作");thread2.start();// 同理,这行代码是在 main 线程中调用的,所以是 main 线程等待 thread2 线程结束,main 线程才能继续往下执行下面的打印代码thread2.join();System.out.println("王五工作结束了");}
}

在这里插入图片描述

当把 join 方法注释掉:

class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable target = () -> {for (int i = 0; i < 4; i++) {try {System.out.println(Thread.currentThread().getName()+ ": 我还在工作!");Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我结束了!");};Thread thread1 = new Thread(target, "李四");Thread thread2 = new Thread(target, "王五");System.out.println("先让李四开始工作");thread1.start();
//        thread1.join();System.out.println("李四工作结束了,让王五开始工作");thread2.start();
//        thread2.join();System.out.println("王五工作结束了");}
}

在这里插入图片描述

6. 线程休眠-sleep()

有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。

在这里插入图片描述

class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}

Thread.sleep(3 * 1000) 并不是 3s 后就上 CPU 执行 , 而是 3s 内上不了 CPU , 因为 sleep 就被放到阻塞队列里面了,休眠完成后被放入到就绪队列中,而就绪队列中不只 你这一个线程,而是多个线程都是就绪状态,等着上 CPU 执行。
在这里插入图片描述

线程休眠实质上就是将线程放到阻塞队列中。所以休眠是阻塞的一种,会释放 CPU(持有锁的话,并不会释放锁)。

7. 获取当前线程引用

在这里插入图片描述
注意,在哪个线程里面调用 Thread.currentThread() 得到的就是哪个线程
在 main 线程中调用:

class ThreadDemo {public static void main(String[] args) {// 注意,在哪个线程里面调用 Thread.currentThread() 得到的就是哪个线程Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}

在这里插入图片描述

在创建出来的其他线程中调用:

class ThreadDemo {public static void main(String[] args) {// 注意,在哪个线程里面调用 Thread.currentThread() 得到的就是哪个线程Thread thread = new Thread(()->{// 这个是在 创建出来的线程里面调用的System.out.println(Thread.currentThread().getName());});thread.start();}
}

在这里插入图片描述

三. 线程的状态

1. 观察线程的所有状态

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

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

输出:
在这里插入图片描述

  • NEW: 安排了工作, 还未开始行动
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
  • BLOCKED: 表示排队等着其他事情,比如等待锁 (阻塞状态)
  • WAITING: 这几个都表示排队等着其他事情(阻塞状态)
  • TIMED_WAITING: 这几个都表示排队等着其他事情(阻塞状态)
  • TERMINATED: 工作完成了.

为什么要这么细分 ?
因为开发过程中经常遇到程序卡死的情况,分析卡死的原因时,我们可以查看各个线程的状态,从而定位问题。

2. 线程状态和状态转移的意义

在这里插入图片描述

在这里插入图片描述

还是之前的例子:

  • 刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态;

  • 当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行工作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;

  • 当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入 BLOCKED 、 WATING 、 TIMED_WAITING 状态;

  • 如果李四、王五已经忙完,为 TERMINATED 状态。

所以,之前我们学过的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。

3. 观察线程的状态和转移

  1. 观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
  • 使用 isAlive 方法判定线程的存活状态.
class ThreadStateTransfer {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 1000_0000; i++) {}}, "李四");System.out.println(t.getName() + ": " + t.getState()); // NEWt.start();while (t.isAlive()) {System.out.println(t.getName() + ": " + t.getState()); // RUNNABLE}System.out.println(t.getName() + ": " + t.getState()); // TERMINATED}
}
  1. 观察 2: 关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
class ThreadStateTransfer {public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();}
}

通过 jconsole 可以看出 t1 是 TIMED_WAITING,t2 是 BLOCKED
t1:

在这里插入图片描述
t2:

在这里插入图片描述

修改上面的代码, 把 t1 中的 sleep 换成 wait:

class ThreadStateTransfer {public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {// [修改这里就可以了!!!!!]// Thread.sleep(1000);object.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();}
}

使用 jconsole 可以看到 t1 的状态是 WAITING
在这里插入图片描述

结论:

  • BLOCKED 表示等待获取锁, WAITING 和 TIMED_WAITING 表示等待其他线程发来通知.
  • TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒
  1. 观察-3: yield() 大公无私,让出 CPU
class ThreadStateTransfer {public static void main(String[] args) {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("张三");
//                     先注释掉, 再放开
//                     Thread.yield();}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("李四");}}}, "t2");t2.start();}
}

可以看到:

  1. 不使用 yield 的时候, 张三李四大概五五开
  2. 使用 yield 时, 张三的数量远远少于李四

结论:
yield 不改变线程的状态, 但是会重新去排队.

好啦! 以上就是对 Thread 类的 详细讲解 !希望能帮到你 !
评论区欢迎指正 !

相关文章:

【多线程】Thread 类 详解

Thread 类 详解 一. 创建线程1. 继承 Thread 类2. 实现 Runnable 接口3. 其他变形4. 多线程的优势-增加运行速度 二. Thread 类1. 构造方法2. 常见属性3. 启动线程-start()4. 中断线程-interrupt()5. 线程等待-join()6. 线程休眠-sleep()7. 获取当前线程引用 三. 线程的状态1. …...

LINUX 网络管理

目录 一、NetworkManager的特点 二、配置网络 1、使用ip命令临时配置 1&#xff09;查看网卡在网络层的配置信息 2&#xff09;查看网卡在数据链路层的配置信息 3&#xff09;添加或者删除临时的网卡 4&#xff09;禁用和启动指定网卡 2、修改配置文件 3、nmcli命令行…...

refresh rate

1920 x 1080 显卡刷新率 60...

使用 NGINX Unit 实施应用隔离

原文作者&#xff1a;Artem Konev - Senior Technical Writer 原文链接&#xff1a;使用 NGINX Unit 实施应用隔离 转载来源&#xff1a;NGINX 中文官网 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.cn NGINX Unit 特性集的最新动态之一是支持应用隔离&#xff0c;该特…...

2023/09/12 qtc++

实现一个图形类(Shape) &#xff0c;包含受保护成员属性:周长、面积&#xff0c; 公共成员函数:特殊成员函数书写 定义一个圆形类(Circle) &#xff0c;继承自图形类&#xff0c;包含私有属性:半径 公共成员函数:特殊成员函数…...

全科医学科常用评估量表汇总,建议收藏!

根据全科医学科医生的量表使用情况&#xff0c;笔者整理了10个常用的全科医学科量表&#xff0c;可在线评测直接出结果&#xff0c;可转发使用&#xff0c;可生成二维码使用&#xff0c;可创建项目进行数据管理&#xff0c;有需要的小伙伴赶紧收藏&#xff01; 日常生活能力量表…...

了解消息中间件的基础知识

为什么要使用消息中间件&#xff1f; 解耦&#xff1a;消息中间件可以使不同的应用程序通过解耦的方式进行通信&#xff0c;减少系统间的依赖关系提供异步通信&#xff1a;消息中间件可以实现异步消息传递&#xff0c;提高系统的响应性能。流量削峰&#xff1a;消息中间件可以…...

【linux】Linux wps字体缺失、加粗乱码解决

解决wps字体缺失问题 1、下载字体包 git clone https://github.com/iamdh4/ttf-wps-fonts.git2、创建单独放置字体的目录 mkdir /usr/share/fonts/wps-fonts3、复制字体到系统目录下 cp ttf-wps-fonts/* /usr/share/fonts/wps-fonts4、修改字体权限 chmod 644 /usr/share/f…...

每日两题 103二叉树的锯齿形层序遍历(数组) 513找树左下角的值(队列)

103 题目 103 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,…...

ROS2报错:ImportError: cannot import name ‘Log‘ from ‘rosgraph_msgs.msg‘

在使用ros2的bag命令查看数据集信息时报错 Traceback (most recent call last):File "/opt/ros/noetic/bin/rosbag", line 34, in <module>import rosbagFile "/opt/ros/noetic/lib/python3/dist-packages/rosbag/__init__.py", line 33, in <mo…...

【Vue】Vue中的代码分为哪几种类型?

在 Vue 中的代码可以分为以下几种类型&#xff1a; 1.模板代码 模板代码是 Vue 中用来生成 HTML 的一种语法&#xff0c;可以通过 Vue 的模板语法和指令来动态渲染页面。模板代码一般写在 Vue 组件的 template 标签中。 2.JavaScript 代码 JavaScript 代码是 Vue 组件中用来…...

es6中includes用法

js中的includes用法 1.数组 includes 可以判断一个数组中是否包含某一个元素&#xff0c;并返回true 或者false [a,b,c].includes(a) true [a,b,c].includes(1) false includes可以包含两个参数&#xff0c;第二个参数表示判断的起始位置 起始位置第一个数字是0。 2.字符串 …...

QT中QRadioButton实现分组C++

通过对QRadioButton组件进行分组可解决QRadioButton组件的互斥性 实现如下。 假设已设计好UI并且有UI代码情况&#xff1a; 头文件引用&#xff1a; #include <QButtonGroup> 分组功能 &#xff0c;cpp文件代码实现&#xff1a; Your_Project::Your_Project(QWidge…...

kafka实战报错解决问题

需求 在一个在线商城中&#xff0c;用户下单后需要进行订单的处理。为了提高订单处理的效率和可靠性&#xff0c;我们使用Kafka来实现订单消息的异步处理。当用户下单后&#xff0c;订单信息会被发送到Kafka的一个Topic中&#xff0c;然后订单处理系统会从该Topic中消费订单消…...

vite+react 使用 react-activation 实现缓存页面

对应的版本 "react": "^18.2.0", "react-activation": "^0.12.4", "react-dom": "^18.2.0", "react-router-dom": "^6.15.0",react-activation 这是一个npm包&#xff0c;在react keep alive…...

【android 蓝牙开发——蓝牙耳机】

【android 蓝牙开发——传统蓝牙】 【android 蓝牙开发——BLE&#xff08;低功耗&#xff09;蓝牙 2021-10-09更新】 总结一下蓝牙开发的基本使用以及蓝牙耳机的断开和链接。 所以需权限&#xff1a; <uses-permission android:name"android.permission.ACCESS_FIN…...

Golang goroutine 进程、线程、并发、并行

goroutine 看一个需求 需求&#xff1a;要求统计1-200000000000的数字中&#xff0c;哪些是素数? 分析思路&#xff1a; 1)传统的方法&#xff0c;就是使用一个循环&#xff0c;循环的判断各个数是不是素数&#xff08;一个任务就分配给一个cpu去做&#xff0c;这样很不划算…...

如何做到安全上网

随着信息化的发展&#xff0c;企业日常办公越来越依赖互联网&#xff0c;而访问互联网过程中&#xff0c;会遇到各种各样不容忽视的风险&#xff0c;例如员工主动故意的数据泄漏&#xff0c;后台应用程序偷偷向外部发信息&#xff0c;木马间谍软件的外联&#xff0c;以及各种挖…...

优维低代码实践:菜单

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…...

git merge 如何撤销

如果只是 git merge 未进行其他 git 操作&#xff0c;可以使用 git merge --abort 撤销如果 git merge 之后&#xff0c;再 git add&#xff0c;可以使用 git reset HEAD 或 git reset HEAD file (前者多个文件&#xff0c;后者单个文件)如果 git merge 之后&#xff0c;再 git…...

解读package.json 中的功能

使用 npm init 比较全 一步一步的走,用于完成 package.json 中的各个声明 npm init -y 生成简易的模板下面解读下 package.json 中的功能"version": "1.0.0", //版本号1. 主版本号&#xff1a;非常大的改动 vue2 和 vue3 的改变 2. 功能的升级&#xff0c;…...

UMA 2 - Unity Multipurpose Avatar☀️四.UMA人物部位的默认颜色和自定义(共享)颜色

文章目录 🟥 人物颜色介绍1️⃣ 使用默认颜色2️⃣ 使用自定义颜色🟧 UMA自定义颜色的作用🟨 自定义颜色还可作为共享颜色🟥 人物颜色介绍 UMA不同部位的颜色分为默认的内置颜色和我们新定义的颜色. 1️⃣ 使用默认颜色 比如不勾选UseSharedColor时,使用的眼睛的默认…...

phpstorm配置php运行环境

1&#xff0c;首先安装phpstrom&#xff0c;按照提示的步骤一步一步来就行 2&#xff0c;新建一个项目然后在里面找到这个位置 3&#xff0c;找到php所在的位置&#xff0c;找不到就直接在搜索框中搜索 4&#xff0c;这里要配置php的运行环境&#xff0c;一定要记得自己安装软…...

算法训练营day49|动态规划 part10:(LeetCode 121. 买卖股票的最佳时机、122.买卖股票的最佳时机II)

121. 买卖股票的最佳时机 题目链接&#x1f525; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大…...

Swagger 使用教程

Swagger 官网&#xff1a; API Documentation & Design Tools for Teams | Swagger 整合swagger 依赖&#xff1a; springfox-swagger2 springfox-swagger-ui <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</a…...

单例模式-饿汉模式、懒汉模式

单例模式&#xff0c;是设计模式的一种。 在计算机这个圈子中&#xff0c;大佬们针对一些典型的场景&#xff0c;给出了一些典型的解决方案。 目录 单例模式 饿汉模式 懒汉模式 线程安全 单例模式 单例模式又可以理解为是单个实例&#xff08;对象&#xff09; 在有些场…...

UG\NX二次开发 复制3元素的double数组到另一个数组 UF_VEC3_copy

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 简介: UG\NX二次开发 复制3元素的double数组到另一个数组 UF_VEC3_copy。仔细看第二段代码 。 效果: 代码: #include "me.hpp"void ufusr(char* param, …...

骨传导耳机对人体有危险吗?会损害听力吗?

如果在使用骨传导耳机的时候控制好时间和音量&#xff0c;是不会对人体带来危险和造成伤害的。 下面跟大家解释一下为什么骨传导耳机对人体没有危害&#xff0c;最大的原因就是骨传导耳机不需要空气传导&#xff0c;而是通过颅骨传到听觉中枢&#xff0c;传输过程中几乎没有噪…...

Spring Boot @Value读不到Nacos配置中心的值。(properties配置文件)

读不到配置中心的值&#xff0c; 配置中心的配置文件名字&#xff08;Data ID的值&#xff09;要以.properties结尾。 如果是yaml&#xff0c;就以yaml命名。...

Rocky Linux怎么安装mysql

Rocky Linux怎么安装mysql 在Rocky Linux上安装MySQL可以通过以下步骤实现&#xff1a; 更新软件包列表 ⭐️⭐️⭐️必要的&#xff0c;必须更新&#xff0c;更新会顺利很多&#xff01;&#xff01;&#xff01;⭐️⭐️⭐️ 在安装MySQL之前&#xff0c;建议先更新软件包…...

潍坊网站设计制作/营销软件培训

目录查询当前日期往前三十天当天有多少数据查询最近三十天&#xff0c;每天多少数据给用户赋值一个随机日期查询最近三十天内&#xff0c;每个用户多少数据统计各个年龄段的用户数量查询数据按天分组统计查询过去10天订单量查询过去30天订单量&#xff0c;没有订单量的日期也要…...

阿里万网怎么做网站/优化大师有必要安装吗

在GameViewController.swift中重载prefersStatusBarHidden方法&#xff0c;返回true override func prefersStatusBarHidden() -> Bool {return true } 转载于:https://www.cnblogs.com/sandal1980/p/3819074.html...

网站推广软件哪个最实惠/怎么投放广告

分类 爬虫分为定向与不定向基本操作 简单来说就是通过指定的url取出数据发送http请求&#xff1a;基于正则表达式匹配获取内容用BeautifulSoup可以先用requests获取网页内容 之后用BeautifulSoup解析 BeautifulSoup(text,http.parser) 之后可以用find寻找对应项&#xff0c;get…...

查网站排名/淘宝优化关键词的步骤

在介绍USB的驱动开发时,我通常会用用Linux的的USB网络来演示&#xff0c;它兼容RNDIS网络&#xff0c;一台Linux即可充当USB的device端&#xff0c;也可以由另一台&#xff2c;inux来充当&#xff35;&#xff33;&#xff22;的Host端&#xff0c;&#xff28;ost端的也可以由…...

网站做的比较好的公司吗/seo好学吗入门怎么学

修改tomcat/conf目录里面server.xml文件 例如下面这样新增一个8090端口&#xff0c;设置下appBase目录&#xff0c;这样就可以用一个tomcat监听多个端口&#xff0c;每个端口都可以放应用了。我这样新增下面这个配置以后&#xff0c;tomcat就监听了2个端口&#xff08;8080&…...

学做网站论坛vip码/网站排名优化培训哪家好

思路1 遍历棋盘中的每一个位置&#xff0c;对于空位置&#xff0c;把1-9都往里面填&#xff0c;假设当前填入1没有打破条件&#xff08;横向没有重复的点&#xff0c;纵向没有重复的点&#xff0c;方格里也没重复的点&#xff09;&#xff0c;那么当前位置就填1&#xff0c;再…...