【学习日记2023.4.12】之Thread的实现方式和解决线程安全问题的三种办法以及线程通讯问题和Thread的生命周期

本文主要是介绍【学习日记2023.4.12】之Thread的实现方式和解决线程安全问题的三种办法以及线程通讯问题和Thread的生命周期,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 多线程相关概念
  • 2. 多线程的实现方式
    • 2.1 继承Thread
      • 步骤
      • 注意事项和优缺点
      • 内存原理
    • 2.2 实现Runnable接口
      • 步骤
      • 优缺点
    • 2.3 实现Runnable接口(匿名内部类)
    • 2.4 实现Callable接口
      • 问题
      • 步骤
      • 注意事项和优缺点
    • 2.5 三种方式的使用选择
  • 3. Thread常用方法
  • 4. 线程安全问题
    • 4.1 案例
    • 4.2 问题
    • 4.3 如何理解共享数据
  • 5. 解决线程安全问题
    • 5.1 同步代码块
      • 格式
      • 作用
      • 锁对象唯一
    • 5.2 同步方法
    • 5.3 Lock
    • 5.4 线程安全问题小结
  • 6. 线程间通讯
    • 6.1 线程间通讯
      • 线程通讯的方法(等待,唤醒)
    • 6.2 线程通讯的常见模型
      • 生产者消费模型
      • 案例
  • 7. 线程的生命周期
  • 8. 总结

1. 多线程相关概念

  • 并发和并行
    • 并发:在同一时刻,有多个指令在CPU单个核心上 交替 执行
    • 并行:在同一时刻,有多个指令在CPU多个核心上 同时 执行
  • 进程和线程
    • 进程:操作系统中正在运行的一个应用程序。
    • 线程:应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾
  • 线程
    • 单线程:程序中如果只有一条执行流程,那这个程序就是单线程的程序。
    • 多线程:程序中如果有多条执行流程,那这个程序就是多线程的程序。
  • 好处和应用场景
    • 好处
      • 提高CPU的利用率,和处理任务的能力
    • 应用场景
      • 桌面应用程序
      • web服务器应用程序
        请添加图片描述

2. 多线程的实现方式

2.1 继承Thread

步骤

  • 定义一个类,继承Thread类
  • 重写run方法
  • 创建对象
  • 调用start()方法,启动线程
/*演示Java语言创建多线程方式一:继承Thread类注意事项:直接调用run方法,不会启动线程start():1.启动线程2.在新启动的线程中,调用run方法继承Thread类的优缺点:优点:代码书写简单缺点:已经继承了一个类,无法再继承其他类,降低了程序的扩展性*/
public class Demo1 {public static void main(String[] args) {//3.创建线程对象final MyThread myThread = new MyThread();//4.调用start方法,启动新的线程myThread.start();for (int i = 1; i <= 5; i++) {System.out.println("主方法中的代码执行了" + i);}}
}//1.定义类继承Thread类
class MyThread extends Thread{//2.重写run方法@Overridepublic void run() {//run方法中的内容,启动线程后,执行的任务for (int i = 1; i <= 5; i++) {System.out.println("新县城执行了" + i);}}
}

注意事项和优缺点

  • 注意事项
    • 启动线程必须是调用start方法,而不是直接调用run方法
      • 直接调用run方法,不会启动线程
      • start方法的作用
        • 启动线程
        • 并执行run方法
  • 优点: 代码书写简单
  • 缺点: 自定义类继承Thread,无法继续继承其他类,不利于功能的扩展

内存原理

  • 官网:https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.5.2
  • 每个Java虚拟机线程都有一个与线程同时创建的专用Java虚拟机栈。
  • 内存图
    请添加图片描述

2.2 实现Runnable接口

步骤

  • 定义任务类,实现Runnable接口
  • 重写run方法
  • 创建任务对象
  • 把任务对象交给一个线程对象处理
public class Demo2 {public static void main(String[] args) {//3.创建线程对象MyRunnable r = new MyRunnable();//4.把任务类的对象,交给线程类Thread t = new Thread(r);//5.通过线程对象(Thread),调用start方法,启动新的线程t.start();for (int i = 1; i <= 5; i++) {System.out.println("主方法中的代码执行了" + i);}}
}//1.自定义类(任务类)实现Runnable接口
class MyRunnable implements Runnable{//2.重写run方法@Overridepublic void run() {//线程启动后要执行的任务for (int i = 1; i <= 5; i++) {System.out.println("新线程执行了" + i);}}
}

优缺点

  • 优点: 任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强
  • 缺点:需要多一个任务对象

2.3 实现Runnable接口(匿名内部类)

public class Demo3 {public static void main(String[] args) {//匿名内部类的实现方式Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {//线程启动后要执行的任务for (int i = 1; i <= 5; i++) {System.out.println("新线程一执行了" + i);}}});t1.start();//lambda表达式  前提:函数式接口(接口中只有一个抽象方法)Thread t2 = new Thread(() ->{//线程启动后要执行的任务for (int i = 1; i <= 5; i++) {System.out.println("新线程二执行了" + i);}});t2.start();for (int i = 1; i <= 5; i++) {System.out.println("主方法中的代码执行了" + i);}}
}

2.4 实现Callable接口

问题

  • 问题: 假如线程执行完毕后有一些数据需要返回,前两种方式重写的run方法均不能返回结果
  • 解决: JDK5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)
  • 第三种创建方式的优点: 可以返回线程执行完毕后的结果

步骤

  • 创建任务对象
    • 定义一个类实现Callable接口,重写call方法(call方法中的内容,就是线程开启后要执行的任务,并且该方法有返回值)
    • 把Callable类型的对象交给FutureTask
FutureTask构造方法说明
public FutureTask<>(Callable call)把Callable对象交给成FutureTask
  • FutureTask交给交给Thread
  • 调用Thread对象的start方法启动线程
  • 线程执行完毕后,通过FutureTask对象的get方法获取线程任务的执行结果
FutureTask提供的方法说明
public V get() throws Exception获取线程执行call方法返回的结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*注意事项及其优缺点:get获取线程执行完成结束后的结果如果线程没结束,死等(阻塞),所以get方法一定要写在start()之后优点:能够获取线程结束后的结果缺点:编码复杂*/
public class Demo1 {public static void main(String[] args) throws ExecutionException, InterruptedException {//3.创建Callable实现类对象MyCallable c = new MyCallable();//4.Callable实现类对象 交给FutureTaskFutureTask<String> f1 = new FutureTask<>(c);//lambda表达式FutureTask<String> f2 = new FutureTask(()->{for (int i = 1; i <= 50; i++) {System.out.println("B向女孩表白的第" + i + "次");}return "喔同意了!";});//5.FutureTask交给ThreadThread t1 = new Thread(f1);Thread t2 = new Thread(f2);t1.start();//开启线程t2.start();//开启线程//6.FutureTask调用get方法获取call方法的返回值final String rusult1 = f1.get();//等第一个线程结束后的结果final String rusult2 = f2.get();//等第二个线程结束后的结果System.out.println("女孩回应A:" + rusult1);System.out.println("女孩回应B:" + rusult2);}
}//1.定义类实现Callable接口
//Callable接口中的泛型,和线程结束后得到结果的类型一致
class MyCallable implements Callable{//2.重写call方法@Overridepublic String call() throws Exception {//是线程开启后执行的任务,并且带有返回值的//模拟向女孩表白for (int i = 1; i <= 50; i++) {System.out.println("A向女孩表白的第" + i + "次");}return "其实你是个好人!";}
}

注意事项和优缺点

  • 注意事项: FutureTask中的get方法会等待线程运行结束,再获取结果.所以不能写到线程开启start方法之前
  • 优点: 实现接口,可以继续继承其他类、实现其他接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果
  • 缺点: 编码复杂

2.5 三种方式的使用选择

  • 前两种方式,哪种方便用哪种
  • 如果需要获取线程执行后的返回结果,只能用第三种方式

3. Thread常用方法

Thread提供的常用方法说明
public void start()启动线程
public String getName()获取线程的名字,线程名字默认Thread-编号
public void setName(String name)为线程设置名字
public static Thread currentThread()获取当前执行的线程对象
public static void sleep(long time)让当前线程的线程休眠多少毫秒后,再继续执行
public final void join()让调用当前这个方法的线程先执行完
Thread提供的构造方法说明
public Thread(String name)指定线程名字
public Thread(Runnable target)封装Runnable对象成为线程对象
public Thread(Runnable target,String name)封装Runnable对象成为线程对象,并指定线程名字
public class Demo1 {public static void main(String[] args) throws ExecutionException, InterruptedException {//3.创建Callable实现类对象MyCallable c = new MyCallable();//4.Callable实现类对象 交给FutureTaskFutureTask<String> f1 = new FutureTask<>(c);//lambda表达式FutureTask<String> f2 = new FutureTask(()->{for (int i = 1; i <= 50; i++) {System.out.println(Thread.currentThread().getName() + "向女孩表白的第" + i + "次");}return "喔同意了!";});//5.FutureTask交给ThreadThread t1 = new Thread(f1,"男孩A");Thread t2 = new Thread(f2);t2.setName("男孩B");t1.start();//开启线程t1.join();//让此线程执行完,再执行其他线程t2.start();//开启线程//6.FutureTask调用get方法获取call方法的返回值final String rusult1 = f1.get();//等第一个线程结束后的结果final String rusult2 = f2.get();//等第二个线程结束后的结果System.out.println("女孩回应" + t1.getName() + ":" + rusult1);System.out.println("女孩回应" + t2.getName() + ":" + rusult2);}
}//1.定义类实现Callable接口
//Callable接口中的泛型,和线程结束后得到结果的类型一致
class MyCallable implements Callable{//2.重写call方法@Overridepublic String call() throws Exception {//是线程开启后执行的任务,并且带有返回值的//模拟向女孩表白for (int i = 1; i <= 50; i++) {System.out.println(Thread.currentThread().getName() + "向女孩表白的第" + i + "次");}return "其实你是个好人!";}
}

4. 线程安全问题

4.1 案例

  • 需求: 电影院,一共100张票,三个窗口【同时】售票,用多线程模拟电影院卖票
public class Demo1 {public static void main(String[] args) {MyThread t1 = new MyThread("窗口一");t1.start();MyThread t2 = new MyThread("窗口二");t2.start();MyThread t3 = new MyThread("窗口三");t3.start();}
}class MyThread extends Thread{private static int ticket = 100;//总票数public MyThread() {}public MyThread(String name) {super(name);}//如果父类(接口)中定义的方法声明上没有 throws//重写的方法就不能使用throws来处理异常,不能使用try…catch@Overridepublic void run() {//线程开启后执行的任务,卖票//不断的卖票,死循环while (true){if (ticket <= 0){break;}else {try {Thread.sleep(100);//模拟出票延迟时间} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(getName() + "开始卖票了,还剩:【" + ticket + "】张票");}}}
}

4.2 问题

  • 模拟售票时间,卖票时 线程休眠100毫秒
  • 现象: 出现负数票,重复票
  • 原因: 多个线程操作了共享资源
  • 什么是线程安全问题: 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

4.3 如何理解共享数据

  • 成员变量,多个线程操作同一个成员变量

5. 解决线程安全问题

5.1 同步代码块

格式

synchronized(锁对象) {
// 操作共享数据的代码
}

作用

  • 底层:线程自动获得锁,释放锁
  • 当有线程进入同步代码块时,锁住(该线程获得锁)
  • 当同步代码块中没有线程执行,锁打开(释放锁)
  • 作用:保证同时只有一个线程在操作共享数据
public class Demo1 {public static void main(String[] args) {MyThread t1 = new MyThread("窗口1");t1.start();MyThread t2 = new MyThread("窗口2");t2.start();MyThread t3 = new MyThread("窗口3");t3.start();}
}
class MyThread extends Thread{private static int ticket = 100;//总票数private static Object obj = new Object();//锁对象必须要求是唯一的public MyThread() {}public MyThread(String name) {super(name);}//如果父类(接口)中定义的方法声明上没有 throws//重写的方法就不能使用throws来处理异常,不能使用try…catch@Overridepublic void run() {//线程开启后执行的任务,卖票//不断的卖票,死循环while (true){synchronized (obj) {if (ticket <= 0){break;}else {ticket--;System.out.println(getName() + "开始卖票了,还剩:【" + ticket + "】张票");}}try {Thread.sleep(100);//模拟出票延迟时间} catch (InterruptedException e) {e.printStackTrace();}}}
}

锁对象唯一

  • 注意: 如果锁对象不唯一,相当于没有锁
private static Object obj = new Object();//锁对象必须要求是唯一的

5.2 同步方法

  • synchronized修饰普通方法:锁对象是this
public synchronized void method(){// 操作共享数据的代码
}
  • synchronized修饰静态方法:锁对象是类名.class
public static synchronized void method(){// 操作共享数据的代码
}
public class Demo2 {public static void main(String[] args) {//1.创建Runnable对象MyRunnable m = new MyRunnable();//只创建了一个实例对象,故MyRunnable类中成员ticket可不为static//2.创建三个线程对象Thread t1 = new Thread(m,"12306官网");t1.start();Thread t2 = new Thread(m,"携程官网");t2.start();Thread t3 = new Thread(m,"飞猪官网");t3.start();}
}class MyRunnable implements Runnable{private int ticket = 100;//总票数
//    private int count = 0;@Overridepublic void run() {while (true){boolean b = getTicketResult();if (b){break;}}}//如果改方法返回true,表示票卖完了//如果改方法返回false,表示票还有剩余public synchronized boolean getTicketResult(){Thread thread = Thread.currentThread();if (ticket == 0){return true;}else {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}ticket--;
//            count++;System.out.println(thread.getName() + "开始卖票了,还剩" + ticket + "张票");//"已经卖了" + count + "张票,还剩"return false;}}
}

5.3 Lock

  • JDK5提供的锁对象,显示的加锁和释放锁
    • Lock为接口
    • ReentrantLock为具体的实现类
  • 格式
Lock lock = new ReentrantLock();
lock.lock(); // 显式加锁
// 操作共享数据的代码
lock.unlock(); // 显式释放锁
  • 注意:
    • ReentrantLock对象要唯一
    • unlock() 要放到finally代码块中保证一定能释放锁
public class Demo3 {public static void main(String[] args) {MyThread3 t1 = new MyThread3("携程官网");t1.start();MyThread3 t2 = new MyThread3("同程官网");t2.start();MyThread3 t3 = new MyThread3("飞猪官网");t3.start();}
}class MyThread3 extends Thread{private static int ticket = 100;//总票数static Lock lock = new ReentrantLock();//static保证lock锁的唯一性private int count = 0;//各个窗口售票张数public MyThread3() {}public MyThread3(String name) {super(name);}//如果父类(接口)中定义的方法声明上没有 throws//重写的方法就不能使用throws来处理异常,不能使用try…catch@Overridepublic void run() {//线程开启后执行的任务,卖票//不断的卖票,死循环while (true){try {lock.lock();// 显示加锁,获得锁if (ticket == 0){break;}else {try {Thread.sleep(10);//模拟出票延迟时间} catch (InterruptedException e) {e.printStackTrace();}ticket--;count++;System.out.println(getName() + "已经卖了" + count + "张票,还剩:【" + ticket + "】张票");}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock(); // 显示的释放锁}}}
}

5.4 线程安全问题小结

请添加图片描述

6. 线程间通讯

6.1 线程间通讯

  • 当多个线程共同操作共享资源时,线程间通过某种方式互相告知自己的状态,以相互协调,避免无效的资源竞争

线程通讯的方法(等待,唤醒)

  • 注意: 下列方法要使用当前同步锁对象进行调用。
Object中的方法说明
void wait()让当前线程等待并释放所占锁,直到其他线程调用notify()方法或者notifyAll()方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程
public class Demo1 {private static Object obj = new Object();public static void main(String[] args) {Thread t1 = new Thread(() ->{synchronized (obj) {Thread thread = Thread.currentThread();//获取当前线程对象System.out.println(thread.getName() + "开始执行了");try {obj.wait();//当前线程在这等待System.out.println(thread.getName() + "结束了");} catch (InterruptedException e) {e.printStackTrace();}}});t1.setName("线程1");t1.start();Thread t3 = new Thread(() ->{synchronized (obj) {Thread thread = Thread.currentThread();//获取当前线程对象System.out.println(thread.getName() + "开始执行了");try {obj.wait();//当前线程在这等待System.out.println(thread.getName() + "结束了");} catch (InterruptedException e) {e.printStackTrace();}}});t3.setName("线程3");t3.start();//try {Thread.sleep(1000);//睡眠确保线程2在线程1和3启动后才行,防止有没有启动导致无法唤醒睡眠} catch (InterruptedException e) {e.printStackTrace();}Thread t2 = new Thread(() ->{synchronized (obj) {Thread thread = Thread.currentThread();//获取当前线程对象System.out.println(thread.getName() + "开始执行了");
//                obj.notify();//唤醒任意等待的一个线程obj.notifyAll();//唤醒所有线程System.out.println(thread.getName() + "结束了");}});t2.setName("线程2");t2.start();}
}

6.2 线程通讯的常见模型

生产者消费模型

  • 生产者线程负责生产数据
  • 消费者线程负责消费生产者产生的数据

案例

  • 厨师线程(生产者)不断的做包子:
    • 当盘子装不下了,唤醒吃货,自己等待
  • 吃货线程(消费者)不断的吃包子
    • 当盘子空了,唤醒厨师,自己等待
  • 桌子上放有盘子(容器)
public class Desk {//盘子容器List<String> list = new ArrayList<>();//向容器中添加包子public synchronized void put() {//判断容器是不是满了if (list.size() == 10){//满了//wait notify notifyAllthis.notifyAll();//要先唤醒其他线程try {this.wait();//再等待} catch (InterruptedException e) {e.printStackTrace();}}else {//没有满Thread thread = Thread.currentThread();list.add("包子");System.out.println(thread.getName() + "做了一个包子,目前包子数为:" + list.size());}}//从容器中取包子public synchronized void gut() {//判断容器是否为空if (list.size() == 0){//空的this.notifyAll();try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {//有包子Thread thread = Thread.currentThread();list.remove(0);//删除集合中的包子System.out.println(thread.getName() + "吃了一个包子,目前包子数为:" + list.size());}}
}
public class Demo1 {public static void main(String[] args) {//0.创建桌子的对象Desk desk = new Desk();//1.创建厨师线程Thread chef1 = new Thread(() -> {while (true){desk.put();try {Thread.sleep(400);} catch (InterruptedException e) {e.printStackTrace();}}});chef1.setName("厨师1");chef1.start();Thread chef2 = new Thread(() -> {while (true){desk.put();try {Thread.sleep(700);} catch (InterruptedException e) {e.printStackTrace();}}});chef2.setName("厨师2");chef2.start();Thread chef3 = new Thread(() -> {while (true){desk.put();try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}});chef3.setName("厨师3");chef3.start();//2.创建吃货线程Thread foodie1 = new Thread(() -> {while (true){desk.gut();try {Thread.sleep(600);} catch (InterruptedException e) {e.printStackTrace();}}});foodie1.setName("吃货1");foodie1.start();Thread foodie2 = new Thread(() -> {while (true){desk.gut();try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}}});foodie2.setName("吃货2");foodie2.start();}
}

7. 线程的生命周期

  • 线程的六种状态和相互转换
    请添加图片描述

  • Thread.State 内部枚举类

状态枚举项状态说明阶段
NEW新建线程刚被创建,但是并未启动。
RUNNABLE可运行线程已经调用了start()
BLOCKED阻塞线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;
WAITING无限等待一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒
TIMED_WAITING计时等待同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态
TERMINATED结束因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
  • 线程对象调用getState()方法可以获取线程的状态
public class Demo {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {System.out.println("线程创建了");try {Thread.sleep(1000);  // t线程睡1秒钟} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(t1.getState()); // 什么状态?  NEW 新建状态t1.start();System.out.println(t1.getState()); // 什么状态?  RUNNABLE 可运行状态// 主线程睡500毫秒Thread.sleep(500);System.out.println(t1.getState()); // 什么状态?  TIMED_WAITING 计时等待}
}

8. 总结

请添加图片描述

这篇关于【学习日记2023.4.12】之Thread的实现方式和解决线程安全问题的三种办法以及线程通讯问题和Thread的生命周期的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/252766

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo