【JavaEE初阶 -- 多线程】

认识线程(Thread)+Thread类及常见方法

  • 1.认识线程(Thread)
    • 1.1 线程
    • 1.2 进程和线程的关系和区别
    • 1.3 Java的线程和操作系统线程的关系
    • 1.4 创建线程
  • 2. Thread类及常用的方法
    • 2.1 Thread的常见构造方法
    • 2.2 Thread的几个常见属性
    • 2.3 启动一个线程-start()
    • 2.4 终止一个线程
    • 2.5 等待线程(结束)
    • 2.6 获取当前线程引用
    • 2.7 线程的状态
    • 2.8 休眠当前线程

1.认识线程(Thread)

1.1 线程

重点:

  • 进程是系统分配资源(CPU、硬盘、内存等)的基本单位
  • 线程是系统调度执行的基本单位

多进程编程时进程在进行频繁的创建和销毁的时候,开销比较大(资源的申请和释放上)。所以,线程就是在进程的基础上,做出了改进,保持了独立调度执行,省去了 分配、释放资源带来的额外开销,多线程并发编程是必须的:

  • 单核CPU想要提高算力,就需要提升到多核CPU,而并发编程能够更加充分利用多核CPU资源。
  • 在某些情况(等待IO),也需要用到并发编程。
  • 虽然多进程也能实现并发编程,但是线程比进程更轻量
  • 创建、销毁、调度线程 比 创建、销毁、调度进程更快
  • 一个线程就是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码,多个线程之间同时执行着多份代码

在这里插入图片描述如上图,是一个进程有一个PCB,但是实际上,一个进程可以有多个PCB,意味着这个线程包含了一个线程组包含了多个线程

  • 操作系统进行多任务调度本质上是在调度PCB线程在系统中的调度规则和进程是一样的。线程的PCB中也有状态、优先级、上下文和记账信息

在这里插入图片描述

  • PCB中一个属性:内存指针,多个线程的PCB的内存指针,指向的是同一个内存空间。意味着只是创建了第一个线程的时候需要从系统分配资源,后续的线程就不必分配,直接共用前面的那份资源就可以了。除内存之外,文件描述符表(操作硬盘)也是多个线程共用一份的。
  • 把能够资源共享的这些线程,分成组,称为线程组,线程组也是进程的一部分。

1.2 进程和线程的关系和区别

假如有这么一个方案:两个房间和桌子,两个老铁吃100只鸡。

  1. 多进程:创建新的进程 就需要申请更多的资源(2 房间桌子,一人吃50)
  2. 多线程:房间和桌子没有增加,吃鸡的人增加了,两个人一人吃50,仍然能提高效率,且资源开销更小
  3. 引入更多的线程:即人多了,吃的鸡少了速度更快了,此时他们共享同一份资源。
  4. 问题1:人越多越好吗,当引入的线程达到一定的数量之后,接着引入线程就提升不了,人多桌子坐不下。即当线程数越来越多的时候,线程之间就会互相竞争,CPU的资源(CPU的核心是有限的),非但不会提高效率,反而会增加调度的开销即并不是线程越多越好
  5. 问题2:重点:线程之间会起冲突,假如2个人同时想吃同一个鸡腿,就会发生冲突。线程之间起了冲突,就可能导致代码中出现一些逻辑上的错误,即线程的安全问题
  6. 问题3:共享资源也会的问题,当一个线程如果抛出异常,没有处理好,就可能会导致整个进程被终止。

进程和线程的关系和区别:
小结

  1. 进程是包含线程的
  2. 每个线程,也是一个独立的执行流,可以执行一些代码,并且单独的参与到CPU调度中(状态、上下文、优先级、记账信息,每个线程都有自己的一份)
  3. 每个进程,有自己的资源,进程中的线程共用这一份资源(内存空间和文件描述符表)
  • 进程是资源分配的基本单位,线程是系统调度执行的基本单位
  1. 同一个进程中的线程之间,可能会干扰。从而引起线程安全问题。
  2. 进程和线程之间,不会相互影响。如果同一个进程中的某个线程,抛出异常,是可能会影响到其他线程,会把整个进程的所有线程都异常终止。
  3. 线程也不是越多越好,如果线程太多了,调度的开销可能非常明显。

1.3 Java的线程和操作系统线程的关系

在Java中编写代码一般是使用多线程并发编程,系统提供了多线程编程的api。在Java标准库,把这些api封装了,在代码中就可以使用了。

注意随机调度:抢占式执行。

  1. 一个线程什么时候被调度到CPU上执行,时机是不确定的
  2. 一个线程什么时候从CPU上下来给别人让位,时机也是不确定的

1.4 创建线程

//后面一般是把”跑起来“的程序 称为”进程“  没有运行起来的程序(exe),称为”可执行文件“//1.创建一个自己的类,继承Thread
class MyThread extends Thread {//这里的Thread类,不需要导包就能直接使用,因为在Java标准库中,有一个特殊的包java.lang(默认包含)//此处的run方法,不需要手动调用,会在合适的时机(线程创建好了之后),被jvm自动调用执行// 这种风格的函数,称为”回调函数(callback)“// 方法重写 本质上 是让自己能够对现有的类进行扩展@Overridepublic void run() {//run方法就是该线程的入口方法System.out.println("hello word");}
}// 一个.java文件中,只能有一个public的类
//这个类如果没有public包级作用于. 就是只能在当前包里被其他的类使用
public class ThreadDemo1 {//一个进程中,至少会有一个线程,这个进程中的第一个线程,也就称为”主线程“// mian方法,也就是主线程的入口法public static void main(String[] args) {// 2.根据刚才的类,创建出示例(线程实例,才是真正的线程)Thread t = new MyThread();// 3.调用Thread的start方法,才会真正的调用系统api,在系统内核中创建出线程  线程就会执行上面写好的run方法t.start();}
}
  1. 创建一个自己的类来继承Thread类,重写run方法
class MyThread2 extends Thread {@Overridepublic void run() {// 如果加上throws,修改了方法签名,此时就无法构成”重写“了//父类的run,没有throws这个异常,子类重写的时候,也就不能有throws异常while (true) {//每秒钟(每次sleep唤醒的时间)先执行mian还是先执行thread,随机调度,抢占式执行System.out.println("hello word");try {//没有处理异常的话 在sleep 1000的过程中可能会被提前唤醒Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class TreadDemo2 {//主线程调用start方法后,即立即往下执行打印了,同时,内核就要通过刚才的线程api构建出线程出来,并且执行run//由于创建线程本身也有开销(虽然开销比创建进程低,但是也不是0),//第一轮打印,在创建线程开销本身影响下,导致hellothread会比hello main略慢一点 先打印mian概率更大不是100%//mian线程其实是第一个线程  ,进程创建第一线程开销最大的,剩下的线程开销都比较小,但不是0public static void main(String[] args) { //执行一个线程Thread t = new MyThread2();t.start();  //调用start创建线程之后,沿着main方法继续执行,打印hellomain// 另外一路  进入到线程的run方法,打印hello word// 真正运行程序,可以看到两个循环都在执行// 这两个线程,就是两个独立的执行流while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
  1. 实现Runnable接口,重写run方法
class MyThread3 implements Runnable {@Overridepublic void run() {while (true) {System.out.println("hello runnable");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ThreadDemo3 {public static void main(String[] args) {//只是一段可执行的代码Runnable runnable = new MyThread3();//还是要搭配Thread类,才能真正在系统中创建出线程,这种写法,就是把线程 和要执行的任务进行了解耦合Thread t = new Thread(runnable);t.start();while (true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
  1. 继承Thread,重写run,但是使用的是匿名内部类:
//继承Thread,重写run,但是使用匿名内部类,在类里面定义的类,该类没有名字,意味着不能重复使用
public class ThreadDemo4 {public static void main(String[] args) {// t执行的实例是Thread的子类Thread t = new Thread() {  //{}定义一个类,这个类继承Thread,可以定义子类的属性和方法//此处的主要目的就是重写run方法@Overridepublic void run() {while (true) {System.out.println("hello thread");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. 实现Runnable类,重写run,匿名内部类
public class ThreadDemo5 {public static void main(String[] args) {//Thread构造方法的参数,填写了Runnable的匿名内部类Thread t = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello runnable");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. 重点推荐/使用:使用lambda表达式:
public class TreadDemo6 {public static void main(String[] args) {//() -> 这里的()前面应该有一个函数名,此处作为匿名函数,就没有名字Thread t = new Thread(() -> {while (true) {System.out.println("hello thread");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();}}}
}

2. Thread类及常用的方法

2.1 Thread的常见构造方法

在Java中自己创建的线程,默认按照Thread-0 1 2 3…
线程之间的名字是可以重复的,同一个工作需要多个线程完成时,都可以起一样的名字
在这里插入图片描述

2.2 Thread的几个常见属性

属性: -------------------------获取方法

  • ID ----------------getld()
  • 名称--------------getName()
  • 状态--------------getState()
  • 优先级-----------getPriority()
  • 是否后台线程----isDaemon()
  • 是否存活---------isAlive()
  • 是否被终止------isInterrupted()

2.3 启动一个线程-start()

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

class MyThread extends Thread {@Overridepublic void run() {//run方法就是该线程的入口方法System.out.println("hello word");}
}public class ThreadDemo1 {//一个进程中,至少会有一个线程,这个进程中的第一个线程,也就称为”主线程“// mian方法,也就是主线程的入口法public static void main(String[] args) {// 2.根据刚才的类,创建出示例(线程实例,才是真正的线程)Thread t = new MyThread();// 3.调用Thread的start方法,才会真正的调用系统api,在系统内核中创建出线程  线程就会执行上面写好的run方法t.start();//t.run();  // 这个操作还是在main线程打印hello word}
}

start和run的区别

  • start()是创建一个新的线程
  • run() 这个操作还是在main主线程

2.4 终止一个线程

  1. 怎么让线程提前终止?核心就是:让run方法能够提前结束,取决于 具体代码实现方式
    run方法和main方法执行顺序是不确定的
public class ThreadDemo12 {private static boolean isQuit = false;public static void main(String[] args) {//boolean isQuit = false;  局部变量用不了//lambda表达式 ,变量捕获(lambda表达式/匿名内部类是可以访问到外面的局部变量)// 捕获的变量得是 final,但是final不能修改 所以这里不能使用局部变量Thread t = new Thread(() -> {//lambda表达式,本质上是 函数式接口 =》 匿名内部类//内部类 访问外部类的成员 是可以的,所以这一过程不受到 变量捕获 的影响while (!isQuit) {System.out.println("我是一个线程,工作中");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程工作完毕");});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("让t线程结束");isQuit = true;}
}

通过Thread.currentThread().isInterrupted() 使用一个interrupt方法,来修改刚才标志位的值
在cath语句中,通过处理线程:

  • 1)让线程立刻结束 ,加上break
  • 2)让线程不结束, 不加break
  • 3)让线程执行一些逻辑之后再结束,即写一些代码再加break
    另外通过catch语句对异常进行处理:
  • 尝试自动恢复
  • 记录日志(将异常信息记录到文件中)
  • 发出警报
  • 也有少数的正常的业务逻辑,会依赖到catch(如文件操作中,通过catch来结束循环之类的)
public class ThreadDemo13 {public static void main(String[] args) {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()){System.out.println("我是一个线程,正在工作中");//在执行sleep的过程中,调用interrupt,大概率sleep休眠时间还没到,被提前唤醒了//提前唤醒之后,会做两件事:1.抛出InterruptedException(被catch获取到。)2.清除Thread对象的isinterrupt标志位// 通过interrupt方法已经把标志位设置为true了,但是sleep提前唤醒操作,就把标志位又设为false,此时循环继续执行//想要结束,只需要在catch中加上breaktry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();  //打印异常//加上break,抛出异常之后,线程也会结束break;}}System.out.println("线程执行完毕");});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}//使用一个interrupt方法,来修改刚才标志位的值System.out.println("让 t 线程结束");t.interrupt();}
}

2.5 等待线程(结束)

多个线程的执行顺序是不确定的,是随机调度,抢占式执行。虽然线程底层的调度是无序的,但是在应用程序中,通过一些api来影响线程执行的顺序。比如join(),影响 线程结束 的先后顺序

public class ThreadDemo14 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() ->{for (int i = 0; i < 5; i++) {System.out.println("我是一个线程,正在工作。。");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程执行结束");});t.start();//Thread.sleep(5000);  //线程执行顺序是无序的// 这个操作就是线程等待  main线程中,调用t.join()  让main线程等待t线程结束t.join(); //执行join的时候,就看t线程是否正在运行// 如果t运行中,main线程就会阻塞(main线程就暂时不去参与CPU执行了)// 如果t运行结束,main线程就会从阻塞中恢复过来,并且继续往下执行System.out.println("这是主线程,期望这个日志在t结束后打印");}
}
  • 典型情况:使用多线程并行开发进行一系列的计算,用一个线程阻塞等待上述计算,等待所有的线程都计算完了,最终这个线程汇总结果
  • 主线程通过join等待,这两线程结束,t和t2都执行结束之后,此时主线程才能继续往下执行,才能打印结果和计算时间
// 让主线程创建一个新的线程,由新线程负责完成一系列的运算(1+2+。。+1000)由主线程负责获取到最终结果public class ThreadDemo15 {// t 线程把计算的结果放到result中private static long result = 0;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {// 如果不加一个临时变量来保存每次计算的值,会出现线程安全问题导致每次计算的结果不一样long tmp = 0;for (long i = 1; i <= 50_0000_0000L; i++) {tmp += i;}result += tmp;});Thread t2 = new Thread(() -> {/*try {// 如果把join加到开头,就是先执行t,t2被阻塞,等t执行完再执行t2,此时是串行执行t.join();} catch (InterruptedException e) {e.printStackTrace();}*/long tmp = 0;for (long i = 50_0000_0001L; i <= 100_0000_0000L; i++) {tmp += i;}result += tmp;});t.start(); // start就是立即的在内核中创建出一个新的线程,且和之前的线程是“并发执行”t2.start();long beg = System.currentTimeMillis();// 使用join就会严格按照t执行结果作为等待的条件// 主线程通过join等待这两个线程结束,t和t2都执行结束之后,此时主线程才能继续执行// 如果把join加在这末尾,t和t2并发执行,没啥区别t.join();t2.join();long end = System.currentTimeMillis();// 不加 t.join() 如果主线程直接就打印result,此时得到的结果是无法预期的// 由于线程之间的执行顺序不确定,主线程打印的result可能是还没有开始计算的初始值// 也有可能是计算过程中间结果,也可能是t线程计算完最终结果//加了t.join()之后,结果一定是t线程执行结束的结果System.out.println("result = "+result);System.out.println("time= "+ (end - beg)+" ms");}}

上面写法是创建两线程,并发执行,主线程等待两个线程结束。
而下面这种写法就是先启动第一个线程,等待第一个线程结束,再启动第二个线程,本质上变成了串行执行
在这里插入图片描述

2.6 获取当前线程引用

Thread.currentThread() 获取到当前线程的引用(Thread的引用)

  • 如果是继承Thread,直接使用this拿到线程实例
  • 如果是Runnable或者是lambda的方式,this就拿不到了,此时this已经不再指向Thread对象了
  • 如果是Runnable或者是lambda的方式:使用Thread.currentThread()
class MyThread5 extends Thread {@Overridepublic void run() {// 获取到线程的引用,直接使用thisSystem.out.println((this.getId() + "," + this.getName()));}
}
public class ThreadDemo16 {public static void main(String[] args) throws InterruptedException {MyThread5 t1 = new MyThread5();MyThread5 t2 = new MyThread5();t1.start();t2.start();Thread.sleep(1000);System.out.println(t1.getId()+","+t1.getName());System.out.println(t2.getId()+","+t2.getName());}
}

Runnable或者是lambda的方式:使用Thread.currentThread() 获取线程的引用


public class ThreadDemo17 {public static void main(String[] args) {Thread t1 = new Thread(() -> {Thread t = Thread.currentThread();System.out.println(t.getId()+","+t.getName());});Thread t2 = new Thread(() -> {Thread t = Thread.currentThread();System.out.println(t.getId()+","+t.getName());});t1.start();t2.start();}
}

2.7 线程的状态

  • 就绪:这个线程可以去CPU上执行了(也包含在正在CPU上执行)
  • 阻塞:这个线程暂时不方便去CPU上执行,Java中,针对阻塞状态又进行了细分
  1. NEW :Thread对象已经创建好了,但是还没有调用start方法在系统中创建线程
  2. TERMINATED: Thread对象仍然存在,但是系统内部的线程已经执行完了
  3. RUNNABLE :就绪状态,表示这个线程正在CPU上执行,或者排队等待调度
  4. TIMED_WAITING: 指定时间的阻塞,就在到达一定的时间之后自动解除阻塞
    使用sleep会进入这个状态,使用带有超时时间的join也会
  5. WAITING : 不带时间的阻塞(死等),必须要满足一定的条件,才会解除阻塞,
    join或者wait都会进入WAITING
  6. BLOCKED : 由于锁竞争,引起的阻塞(即会引起线程安全)

在这里插入图片描述

  • 如果发现某个进程卡住了,就可以使用jconsole这个工具(在JDK中的bin目录下),查看这个进程中的一些重要线程的状态和调用栈,通过状态,就可以判断线程是否阻塞,以及阻塞原因
    在这里插入图片描述

2.8 休眠当前线程

public class ThreadDemo18 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("线程运行中。。");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});// 线程启动前,状态就是NEWSystem.out.println(t.getState());t.start();Thread.sleep(500);System.out.println(t.getState());t.join();// 线程运行完成之后,状态就是TERMINAEDSystem.out.println(t.getState());}
}

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

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

相关文章

AI 赋能,第二大脑:一个开源的个人生产力助手 | 开源日报 No.195

QuivrHQ/quivr Stars: 28.3k License: Apache-2.0 quivr 是一个个人生产力助手&#xff0c;利用生成式人工智能技术作为第二大脑。 快速高效&#xff1a;设计迅捷高效&#xff0c;确保快速访问数据。安全可靠&#xff1a;您的数据由您掌控&#xff0c;始终安全。跨平台兼容性…

Unity3d C#实现文件(json、txt、xml等)加密、解密和加载(信息脱敏)功能实现(含源码工程)

前言 在Unity3d工程中经常有需要将一些文件放到本地项目中&#xff0c;诸如json、txt、csv和xml等文件需要放到StreamingAssets和Resources文件夹目录下&#xff0c;在程序发布后这些文件基本是对用户可见的状态&#xff0c;造成信息泄露&#xff0c;甚至有不法分子会利用这些…

Ajax+Axios+前后端分离+YApi+Vue-ElementUI组件+Vue路由+nginx【全详解】

目录 一.Ajax技术 二. Axios 三.前后台分离开发介绍 四. YAPI 五.前端工程化 六.vue工程的目录结构 七.Vue项目核心文件 八.Vue组件库ElementUI AboutView.vue最终代码 AboutView.vue最终代码 九.Vue路由 十.案例 十一.nginx介绍 一.Ajax技术 1.Ajax概述 Ajax: 全…

离线数仓(五)【数据仓库建模】

前言 今天开始正式数据仓库的内容了, 前面我们把生产数据 , 数据上传到 HDFS , Kafka 的通道都已经搭建完毕了, 数据也就正式进入数据仓库了, 解下来的数仓建模是重中之重 , 是将来吃饭的家伙 ! 以及 Hive SQL 必须熟练到像喝水一样 ! 第1章 数据仓库概述 1.1 数据仓库概念 数…

C语言:通讯录(纯代码)

目录 背景&#xff1a;VS2019编译器 创建文件&#xff1a; contact.h代码&#xff1a; test.c代码&#xff1a; contact.c代码&#xff1a; 背景&#xff1a;VS2019编译器 创建文件&#xff1a; contact.h代码&#xff1a; #pragma once#include <string.h> #includ…

FPGA的时钟资源

目录 简介 Clock Region详解 MRCC和SRCC的区别 BUFGs 时钟资源总结 简介 7系列FPGA的时钟结构图&#xff1a; Clock Region&#xff1a;时钟区域&#xff0c;下图中有6个时钟区域&#xff0c;用不同的颜色加以区分出来 Clock Backbone&#xff1a;从名字也能看出来&#x…

安卓 OpenGL ES 学习笔记

文章目录 OpenGL 学习笔记OpenGL 是什么&#xff1f;OpenGL ES是什么&#xff1f;怎么用&#xff1f;hello world如何实现动画效果 参考文章 OpenGL 学习笔记 OpenGL 是什么&#xff1f; OpenGL&#xff08;Open Graphics Library&#xff09;是一个跨平台的图形编程接口&…

Mint_21.3 drawing-area和goocanvas的FB笔记(七)

FreeBASIC gfx 基本 graphics 绘图 8、ScreenControl与屏幕窗口位置设置 FreeBASIC通过自建屏幕窗口摆脱了原来的屏幕模式限制&#xff0c;既然是窗口&#xff0c;在屏幕坐标中就有它的位置。ScreenControl GET_WINDOW_POS x, y 获取窗口左上角的x, y位置&#xff1b;ScreenC…

C++笔记之给枚举类型的变量赋值

C++笔记之给枚举类型的变量赋值 —— 杭州 2024-03-10 code review! 在C++中,你可以在结构体内部定义一个枚举类型,并在创建结构体变量时给枚举类型的变量赋值。下面是一个简单的例子展示了如何做到这一点: 代码 #include <iostream>// 定义结构体 struct MyStru…

如何在Windows系统使用固定tcp公网地址ssh远程Kali系统

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh 远程连接kali! …

在WSL2中安装多个Ubuntu教程

文章目录 前言一、前期准备1、WSL安装2、Docker安装 二、安装第二个Ubuntu系统1.切换为WSL22.获取Ubuntu16.04的tar文件从容器中导出tar 3. 将tar文件导入WSL4. 设置默认用户 总结 前言 适用于 Linux 的 Windows 子系统 (WSL) 是 Windows 的一项功能&#xff0c;可用于在 Wind…

详解Linux例行性工作

例行性工作&#xff08;计划任务&#xff09; 场景&#xff1a; 生活中&#xff0c;我们有太多场景需要使用到闹钟&#xff0c;比如早上7点起床&#xff0c;下午4点开会&#xff0c;晚上8点购物&#xff0c;等等。再Linux系统里&#xff0c;我们同样也有类似的需求。比如我们…

VS Code搭建windows+远程Linux上Docker的开发环境

在本地windows桌面系统远程Linux上Docker搭建开发环境主要步骤如下&#xff1a; 一、安装vs code和插件 在windows系统上安装vs code&#xff0c;并安装好remote-ssh、dev-container插件&#xff0c;也可以直接安装Remote Development&#xff0c;他会默认把vs code远程的几种…

【硬件基础】STM32F103C8T6芯片引脚定义及功能介绍

1、引脚图片 2、引脚定义图 3、引脚功能介绍 3.1引脚定义图解释 上表中&#xff0c;S表示电源&#xff0c;IO表示输入输出&#xff0c;FT表示容忍电压可达5V&#xff0c;没有FT的只能达3.3V。 VBAT&#xff0c;备用电源引脚&#xff0c;当系统断电时&#xff0c;备用电源可给…

GEE错误——Landsat9数据集进行去云操作后显示白板

问题 我遇到了一些有关 Landsat9 图像中的云遮蔽和图像处理的问题。我正在分享我所使用的代码以及我感兴趣的区域(资产)。请帮我解决这个问题。我是一名 GEE 学习者。问题:最终图像在大面积上有云状覆盖。 这里我们查看了搜索出的代码发现并不是没有数据集导致的,该区域有…

springcloud第3季 consul服务发现注册,配置中心2

一 consul的作用 1.1 为何使用注册中心 为何要用注册中心&#xff1f; 1.A服务调用B服务&#xff0c;使用ip和端口&#xff0c;如果B服务的ip或者端口发生变化&#xff0c;服务A需要进行改动&#xff1b; 2.如果在分布式集群中&#xff0c;部署多个服务B&#xff0c;多个服…

运动想象 (MI) 迁移学习系列 (3) : MSFT

运动想象迁移学习系列:MSFT 0. 引言1. 主要贡献2. 数据增强方法3. 基于度量的空间滤波转换器3.1 空间过滤3.2 脑电图ViT3.2.1 变压器编码器层3.2.2 基于度量的损失函数 4. 实验结果4.1 消融实验4.2 基线任务对比4.3 跨主题 5. 总结欢迎来稿 论文地址&#xff1a;https://www.s…

用C语言执行SQLite3的gcc编译细节

错误信息&#xff1a; /tmp/cc3joSwp.o: In function main: execSqlite.c:(.text0x100): undefined reference to sqlite3_open execSqlite.c:(.text0x16c): undefined reference to sqlite3_exec execSqlite.c:(.text0x174): undefined reference to sqlite3_close execSqlit…

对中国境内所有地区KFC门店基本信息的统计(简略版)

我们要获取每个地区的kfc信息就要先获取中国一共有哪些地区 中国所有城市名称获取 import requests from lxml import etreewith open(f./省份.txt, w) as fp:fp.write() with open(f./城市.txt, w) as fp:fp.write()url1http://www.kfc.com.cn/kfccda/storelist/index.aspx#…

如何基于 esp-at 固件测试 TCP (UART 转 WiFi 透传)吞吐?

测试工具&#xff1a; windows/Ubuntu/Android&#xff08;电脑或手机与 ESP 开发板连接相同路由器&#xff09;iperf2 工具ESP 系列的开发板USB-TTL 串口调试工具路由器 测试固件&#xff1a; AT 固件 AT 固件硬件接线说明 不同环境下的 Iperf 工具安装说明 Iperf 工具用于…