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

JavaEE-多线程基础知识

文章目录

  • 前言与回顾
  • 创建一个多线程
    • 线程的创建以及运行机制简述
    • step1: 继承Thread类
    • step2: 实现Runable接口
    • step3: 基于step1使用匿名内部类
    • step4: 基于step2使用匿名内部类
    • step5: 基于step4使用lambda表达式(推荐)
  • Thread的常见方法
    • 关于jconsole监视线程的工具
    • 构造方法解析
    • 获取 ID / 名称
    • 查看 / 设置后台线程
    • 查看线程的状态
    • 获取 / 设置线程的优先级
    • 查看线程存活
  • 中断线程以及阻塞唤醒机制
    • 使用自定义标志位终止线程
      • lambda中的变量捕获
    • 使用interrupt
      • interrupt方法对于阻塞的打断
  • 等待一个线程(join方法)
    • join方法的基本说明
    • join方法的使用及线程状态观察
  • 线程休眠机制
    • 如何获取线程的引用
    • sleep方法
    • sleep(0) 和 yield()

前言与回顾

基础的一些关于线程/进程的一些基础的概念, 已经在之前的帖子中有过解释
简单复习一下

  • 线程是轻量级的进程(进程太重了, 创建销毁代价大)
  • 进程包括线程
    进程是操作系统资源分配的基本单位
    线程是操作系统调度执行的基本单位
  • 线程的调度是操作系统进行的, 在应用层无法进行干预, 也无法感知
  • 线程是操作系统级别的概念, 操作系统内核实现了这种机制并封装提供用户一些API
    Java中的Thread类可以视为是对操作系统的API的进一步封装和抽象

创建一个多线程

线程的创建以及运行机制简述

我们创建线程的最基本的方法其实是两种(后续还有)

  • 通过定义一个类继承Thread类重写run方法
  • 通过定义一个类实现Runnable接口重写run方法

run方法相当于主线程的main, 作为每一个线程的程序运行的入口, 我们通过start方法启动一个线程, 这时候操作系统的内部会真正的创建一个线程(源码层面是去调用的start0native修饰的本地方法, 自行调用cpp动态链接库), start0会调用run方法, 所以这相当于是一种回调函数的机制
但是我们提供了五种创建线程的方式, 其实本质都是基于上述两种(后续还要其他方法)
关于start的源码

public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/if (threadStatus != 0)throw new IllegalThreadStateException();/* Notify the group that this thread is about to be started* so that it can be added to the group's list of threads* and the group's unstarted count can be decremented. */group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}}// 本地方法private native void start0();

也就是真正起作用的是start0, 如果好奇native修饰的方法的具体实现, 可以去JDK官网上自行查看JVM源码

step1: 继承Thread类

实现代码如下, 我们让每一个线程都睡眠1000ms便于观察

/*** 第一种创建线程的方式* 继承Thread类重写run方法*/class MyThread extends Thread{@Overridepublic void run() {while(true){System.out.println("hello thread!");// 睡眠一秒钟try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ThreadTest {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();}}}
}

开始运行, 可以明显的看到线程t和线程main在交替的执行, 这也说明了是我们的底层的抢占式调度模型在起作用, 多个线程不断地抢夺cpu时间片

在这里插入图片描述

step2: 实现Runable接口

每一个逻辑加上一个sleep方法休眠便于观察

/*** 第二种创建线程的方式* 定义一个类实现Runnable接口*/
class MyRunnable implements Runnable{@Overridepublic void run() {while(true){System.out.println("hello thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadTest {public static void main(String[] args) throws InterruptedException {// 创建一个线程tThread t = new Thread(new MyRunnable());// 开启一个线程tt.start();// 主线程的逻辑while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

在这里插入图片描述

step3: 基于step1使用匿名内部类

/*** 基于第一种方法使用匿名内部类*/
public class ThreadTest {public static void main(String[] args) throws InterruptedException {// 创建t线程Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("hello thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};// 开启t线程t.start();// 主线程的逻辑while (true) {System.out.println("hello main!");Thread.sleep(1000);}}
}

step4: 基于step2使用匿名内部类

public class ThreadTest {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(new Runnable() {@Overridepublic void run() {while(true){System.out.println("hello thread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});// 开启线程 tt.start();// 主线程的逻辑如下while(true){System.out.println("hello main!");Thread.sleep(1000);}}
}

step5: 基于step4使用lambda表达式(推荐)

lambda表达式本质上就是一个匿名的函数, 最主要的用途就是作为回调函数, 很多语言都有, 只不过叫法有差异而已

  • 底层编译器会对lambda表达式进行处理, 创建了一个匿名的函数式接口的子类, 并且创建了对应的实例并且重写里面的方法

代码实例

/*** 使用lambda表达式进行问题的描述*/
public class ThreadTest {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello t!!!");}}, "t");// 开启t线程t.start();// 主线程中的逻辑while(true){Thread.sleep(1000);System.out.println("hello main!!!");}}
}

在这里插入图片描述
这里可以看到, 我们的两个线程正在交替的执行逻辑
我们推荐这种方式进行平时的线程的测试, 因为比较的轻量级

Thread的常见方法

关于jconsole监视线程的工具

构造方法解析

是我们的JDK中自带的一个检测当前线程的工具, 在我们的JDK安装路径中的bin目录下, 可以在这个目录下找到这个检测的工具
在这里插入图片描述
我们开启一个多线程的逻辑, 我们这里的测试代码是上面的step5创建线程的方法, 可以先去看一下…
打开之后是下面的窗口
在这里插入图片描述
这里我们查看 t线程和main线程

在这里插入图片描述
在这里插入图片描述
这里我们可以看到线程的状态
比如现在的两个线程都位于TIMED_WAITING状态(sleep(1000))
还可以查看当前线程的堆栈跟踪状态是什么


这里其实还说明了一些问题, 我们的后台其实存在多个线程(本质上是后台线程, 比如跟网络通讯相关的线程, 还有跟垃圾回收 GC 相关的一些线程) 这些线程维持我们 Java 程序的正常执行


在这里插入图片描述
这张图是JDK的帮助文档的截图, 我们逐一解释一下里面的一些主要的方法(关于线程组创建线程的方法我们暂时略过, 后期回来继续说, 其实就是把线程进行分组)

方法签名描述
Thread()直接创建线程对象
Thread(Runnable target)使用实现了Runnable接口的对象来创建线程
Thread(String name)创建线程并传入一个名字(便于使用jconsole进行监视)
Thread(Runnable target, String name)前两种的结合
Thread(ThreadGroup group, Runnable target)使用线程组来创建线程(现阶段了解)

获取 ID / 名称

对于每一个线程来说, 都有一个独一无二的 ID (标识码, 类似PID)
对于每个线程来说, 程序开发者可以选择指定一个名字(或者系统自动分配)

  • getID: 获取当前线程的标识码
  • getName: 获取当前线程的名称
public class ThreadTest {public static void main(String[] args) throws InterruptedException{// 创建一个t线程Thread t = new Thread(() -> {while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName());}}, "t线程");// 开启t线程t.start();// 主线程中的逻辑while(true){Thread.sleep(1000);System.out.println(Thread.currentThread().getId() + " " + Thread.currentThread().getName());}}
}

在这里插入图片描述
可以观测到此时的随机调度的线程的 ID 以及 name

查看 / 设置后台线程

后台线程其实就是守护线程, 其实就是进程的"幕后人员"

  • setDaemon(boolean) : 传入一个true把当前线程设置为守护线程(start前)
  • isDaemon(): 查看当前线程是否是守护线程

守护线程是当所有的用户线程全部结束之后, 自动就结束了, 但是如果守护线程自己选择结束了, 那也算是结束了, 所以我们的守护线程一般用于链接网络, 维护数据库日志等等场景

class ThreadTest01 {public static void main(String[] args) throws InterruptedException {// 创建一个t线程Thread t = new Thread(() -> {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("守护线程执行ing");}}, "t线程");// 设置t线程为守护线程t.setDaemon(true);// 开启t线程t.start();while (true) {Thread.sleep(1000);System.out.println("main线程执行ing");}}
}

在这里插入图片描述
通过jconsole检测也可以发现两个线程的情况, 可以看到两个位置都是TIMED-WAITING状态

查看线程的状态

  • getState(): 查看当前线程的状态(有六种状态)
class ThreadTest01 {public static void main(String[] args) throws InterruptedException {// 创建一个t线程Thread t = new Thread(() -> {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("守护线程执行ing " + Thread.currentThread().getState());}}, "t线程");// 设置t线程为守护线程t.setDaemon(true);// 开启t线程t.start();while (true) {Thread.sleep(1000);System.out.println("main线程执行ing " + Thread.currentThread().getState());}}
}

在这里插入图片描述
实质上有多种状态描述, 我们此时的状态是可运行状态

获取 / 设置线程的优先级

class ThreadTest01 {public static void main(String[] args) throws InterruptedException {// 创建一个t线程Thread t = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 查看当前线程的优先级System.out.println("默认的优先级: " + Thread.currentThread().getPriority());}, "t线程");t.start();System.out.println("最低优先级: " + Thread.MIN_PRIORITY + " 最高优先级: " + Thread.MAX_PRIORITY);}
}

在这里插入图片描述

  • setPriority(): 设置线程的优先级(0 - 10)

这其实是一个概率问题, 定义一个线程抢占cpu时间片的概率, 但不是绝对的

查看线程存活

  • isAlive(): 判断当前线程是否存活

请注意, 线程的存活和线程对象的生命周期一般是不一样的
比如下面的代码

class ThreadTest02{public static void main(String[] args) throws InterruptedException {// 创建一个t线程Thread t = new Thread(() -> {System.out.println("...");}, "t线程");t.start();while(true){Thread.sleep(1000);System.out.println(t.isAlive());}}
}

在这里插入图片描述
此时线程以及结束了(run方法执行结束), 但是线程对象并没有销毁

中断线程以及阻塞唤醒机制

使用自定义标志位终止线程

定义一个static变量作为标志位然后打标记


public class ThreadTest {// 自定义一个标志位打标记private static boolean isFinished = false;public static void main(String[] args) throws InterruptedException {// 使用lambda机制创建一个线程Thread t = new Thread(() -> {while(!isFinished){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName());}}, "t线程");// 开启t线程t.start();// 三秒之后终止t线程Thread.sleep(3000);isFinished = true;}
}

在这里插入图片描述
可以看到 3s 之后终止 t 线程

lambda中的变量捕获

如果上面的代码的标志位是局部变量, 那么我们就触发了lambda表达式中的变量捕获语法
在这里插入图片描述
这个代码我们发现报错了

  • 原因: 这其实就是lambda语法中的变量捕获机制, 对于lambda表达式来说, 本质是一种回调函数, 执行时机很可能是很久之后了, 此时main函数存在与否都不好说, 假设main执行结束了, 此时isFinished变量其实也就销毁了, 那lambda中访问的isFinished变量又是什么呢 ?

  • 解决方案: 为了解决上述的问题, Java中采取的做法是, 在定义lambda的时候, 直接就把变量拷贝一份给lambda中的变量, 所以其实上述代码中, lambda中的isFinished变量跟外部的isFinished的变量, 本质上是两个变量, 这样处理的好处就是, 此时外部的情况不管怎么变, 即使变量销毁了, 对lambda表达式内部也不会造成影响

  • 不允许修改: 根据上面的解释, 如果一边改变, 另一边不变, 这样处理就会对程序员造成更对的困扰, 所以Java这边的做法是, 压根不允许我们对变量的值进行修改, 对于引用变量来说, 不允许指向修改, 但是内部的值是可以修改的…(C++对于这里的操作相对更复杂, 需要你自己定义变量的生命周期以及是否进行拷贝)

  • 和定义外部变量的区别: 那为什么上面的定义一个外部变量就可以修改呢, 因为所属的语法机制不一样, 上面所属的语法体系是内部类访问外部类变量, 当然可以修改

引用变量的场景

在这里插入图片描述

在这里插入图片描述
此时可以修改内部的值, 但是不可以修改引用

使用interrupt

关于终止线程, 我们上面给出了一种修改终止标志位的方案, 但其实Java中的Thread类天然的提供了一个标志位用来进行变量的修改


我们分析一下下面的代码

public class ThreadTest {public static void main(String[] args) throws InterruptedException {// 关于使用内部自带的标志位修改Thread t = new Thread(() -> {while(!Thread.currentThread().isInterrupted()){System.out.println("hello main!!!");;//Thread.sleep(1000);}});// 开启线程tt.start();// 三秒后终止线程tThread.sleep(3000);t.interrupt();}
}

上述代码是可以正常结束线程的, 因为3s之后, 我们把t线程的标志位设置为true, 然后跳出循环…
在这里插入图片描述
在这里插入图片描述
观察一下源码就可以发现, 其实跟我们自行定义标志位的做法是一致的

interrupt方法对于阻塞的打断

我们尝试分析一下下面的代码

public class ThreadTest {public static void main(String[] args) throws InterruptedException {// 关于使用内部自带的标志位修改Thread t = new Thread(() -> {while(!Thread.currentThread().isInterrupted()){System.out.println("hello main!!!");;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});// 开启线程tt.start();// 三秒后终止线程tThread.sleep(3000);t.interrupt();}
}

我们运行的结果如下
在这里插入图片描述
我们尝试打断线程, 但是失败了, 只是在控制台打印了堆栈信息, 但是线程并没有被打断…
这里我们没办法查看sleep方法的源码(native修饰的本地方法)

  • 原因: 其实就是我们在调用interrupt方法的同时, 会唤醒sleep等等类似阻塞效果的方法, 并抛出异常InterruptedException后被唤醒(其实就是唤醒的机制), 并且把标志位interrupted重新设置为false, 所以下次循环还可以进入…
  • 处理逻辑: 那为什么要这样处理呢, 是不是毫无用处多此一举呢… 我认为, 这样处理的逻辑还是相对比较合理的, 因为留给了程序员更多的操作空间, 在被唤醒之后, 程序员可以选择下面三种处理方式
    1 . 直接跳出执行逻辑(比如通过break)
    2 . 执行操作逻辑后再跳出(先执行一段逻辑代码处理, 然后break)
    3 . 忽视这个信号, 继续执行

在这里插入图片描述
上面是我们对于这个终止信号的三种处理态度

等待一个线程(join方法)

join方法的基本说明


由于多个线程之间都是随机调度执行的, 并发执行, 随机调度
所以站在程序员的角度, 我们不喜欢随机性, join方法可以要求多个线程之间的结束的先后顺序…
虽然可以通过sleep来设置等待的时间, 但是这种机制往往是不科学的, 比如我们现在有一个需求, 想要t线程执行结束后, main线程立即开始执行, 这时候使用sleep的合理性就很差了.


在这里插入图片描述
我们这里有三个常见的方法

  • join(): 无时间期限的等待
  • join(long millis): 设置最长的等待时间为millis, 单位为毫秒
  • join(long millis, int nanos): 设置最长的等待期限是毫秒+纳秒(不常用)
    对于第三个方法的说明, 其实做不到那么精确, 除非使用实时操作系统…

第一个进入WAITING状态, 第二三个进入TIMED_WAITING状态

join方法的使用及线程状态观察


public class ThreadTest {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for(int i = 0; i < 5; i++){System.out.println("hello t ! ! !");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}, "t");// 启动t线程t.start();// t执行完毕再执行main线程t.join();System.out.println("hello main ! ! !");}
}

在这里插入图片描述
在t线程执行5s之后, main线程继续执行, 我们使用jconsole工具查看一下情况(修改上面t线程循环次数为50)

在这里插入图片描述
此时t线程处于TIMED_WAITING状态, 因为这个线程的大部分时间都处于sleep(1000)这里…

在这里插入图片描述
观察到main线程处于WAITING状态, 此时正在因为join()产生阻塞(无时间期限)

线程休眠机制

如何获取线程的引用

  • Thread.currentThread(): 静态方法, 返回一个当前线程对象的引用(相当于this关键字)

sleep方法

这个方法没什么可说的, 但是也有需要注意的一点
比如

Thread.sleep(1000);

这个真的是让当前线程休眠 1000ms 吗 ?
其实并没有那么精确, 实际上一般是要略大于 1000ms , 因为sleep(1000), 指的是1000ms之后
该线程再次拥有了抢夺 cpu 时间片的能力, 但是具体需要多长时间能够抢到, 这个其实是一个未知数, 所以一般是要比 1000ms 稍微大一些的…

sleep(0) 和 yield()

在这里插入图片描述

  • yield: 是一个静态方法, 使得当前调用该线程的方法立刻放弃抢夺到的cpu时间片进入就绪状态
  • sleep(0): 和上面yield类似, 都是立刻放弃抢夺到的cpu时间片, 进入抢夺cpu时间片的就绪状态

相关文章:

JavaEE-多线程基础知识

文章目录 前言与回顾创建一个多线程线程的创建以及运行机制简述step1: 继承Thread类step2: 实现Runable接口step3: 基于step1使用匿名内部类step4: 基于step2使用匿名内部类step5: 基于step4使用lambda表达式(推荐) Thread的常见方法关于jconsole监视线程的工具构造方法解析获取…...

Pulid:pure and lightning id customization via contrastive alignment

1.introduction 基于微调的方案,对每个id进行定制需要花费数十分钟。另一项研究则放弃了对每个id进行微调,而是选择在一个庞大的肖像数据集上预训练一个id适配器。这些方法通常利用编码器例如clip来提取id特征,提取的特征随后以特定方式例如嵌入到cross attention集成到基础…...

什么是GraphQL,有什么特点

什么是GraphQL&#xff1f; GraphQL 是一种用于 API&#xff08;应用程序编程接口&#xff09;的查询语言&#xff0c;由 Facebook 在 2012 年开发&#xff0c;并于 2015 年开源。它提供了一种更高效、强大的方式来获取和操作数据&#xff0c;与传统的 RESTful API 相比&#…...

Java项目-基于SpringBoot+vue的租房网站设计与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…...

【SQL Server】华中农业大学空间数据库实验报告 实验三 数据操作

1.实验目的 熟悉了解掌握SQL Server软件的基本操作与使用方法&#xff0c;以及通过理论课学习与实验参考书的帮助&#xff0c;熟练掌握使用T-SQL语句和交互式方法对数据表进行插入数据、修改数据、删除数据等等的操作&#xff1b;作为后续实验的基础&#xff0c;根据实验要求重…...

【大数据学习 | Spark】RDD的概念与Spark任务的执行流程

1. RDD的设计背景 在实际应用中&#xff0c;存在许多迭代式计算&#xff0c;这些应用场景的共同之处是&#xff0c;不同计算阶段之间会重用中间结果&#xff0c;即一个阶段的输出结果会作为下一个阶段的输入。但是&#xff0c;目前的MapReduce框架都是把中间结果写入到HDFS中&…...

ruoyi框架完成分库分表,按月自动建表功能

前提 这个分库分表功能&#xff0c;按月自动建表&#xff0c;做的比较久了&#xff0c;还没上线&#xff0c;是在ruoyi框架内做的&#xff0c;踩了不少坑&#xff0c;但是已经实现了&#xff0c;就分享一下代码吧 参考 先分享一些参考文章 【若依系列】集成ShardingSphere S…...

Antd中的布局组件

文章目录 一、Layout二、Menu三、Grid栅格 布局组件涉及项目框架的搭建&#xff0c;往往被忽略和低关注&#xff0c;毕竟不是经常用到&#xff0c;但是在调整项目结构的时候往往又需要重新设计布局&#xff0c;所以有必要提前归纳分析&#xff1b; 一、Layout Layout导出Sider,…...

一文详解kafka知识点

目录 1、kafka定义 2、消息队列 2.1、产品选择 2.2、应用场景 2.3、消息队列的两种模式 3、kafka架构 4、kafka生产者 4.1、kafka生产者原理 4.2、kafka生产者异步发送 4.3、同步发送 4.4、分区 4.4.1、kafka分区好处 4.4.2、分区策略 4.4.3、自定义分区 4.5、生成吞…...

C语言基础学习:抽象数据类型(ADT)

基础概念 抽象数据类型&#xff08;ADT&#xff09;是一种数据类型&#xff0c;它定义了一组数据以及可以在这组数据上执行的操作&#xff0c;但隐藏了数据的具体存储方式和实现细节。在C语言中&#xff0c;抽象数据类型&#xff08;ADT&#xff09;是一种非常重要的概念&…...

提升性能测试效率与准确性:深入解析JMeter中的各类定时器

在软件性能测试领域&#xff0c;Apache JMeter是一款广泛使用的开源工具&#xff0c;它允许开发者模拟大量用户对应用程序进行并发访问&#xff0c;从而评估系统的性能和稳定性。在进行性能测试时&#xff0c;合理地设置请求之间的延迟时间对于模拟真实用户行为、避免服务器过载…...

施密特正交化与单位化的情形

在考研数学的线性代数部分&#xff0c;施密特正交化和单位化是两种不同的处理向量的方法&#xff0c;它们在特定的情况下被使用。以下是详细说明&#xff1a; 施密特正交化的应用场景 施密特正交化&#xff08;Gram-Schmidt Orthogonalization&#xff09;是一种从线性无关向…...

ROS机器视觉入门:从基础到人脸识别与目标检测

前言 从本文开始&#xff0c;我们将开始学习ROS机器视觉处理&#xff0c;刚开始先学习一部分外围的知识&#xff0c;为后续的人脸识别、目标跟踪和YOLOV5目标检测做准备工作。我采用的笔记本是联想拯救者游戏本&#xff0c;系统采用Ubuntu20.04&#xff0c;ROS采用noetic。 颜…...

2024 APMCM亚太数学建模C题 - 宠物行业及相关产业的发展分析和策略(详细解题思路)

在当下&#xff0c; 日益发展的时代&#xff0c;宠物的数量应该均为稳步上升&#xff0c;在美国出现了下降的趋势&#xff0c; 中国 2019-2020 年也下降&#xff0c;这部分变化可能与疫情相关。需要对该部分进行必要的解释说明。 问题 1: 基于附件 1 中的数据及您的团队收集的…...

C#里怎么样访问文件时间

C#里怎么样访问文件时间 文件时间也是一个关键信息, 因为很多数据处理需要时间来判断数据的有效性,比如股票中的股价, 它是的权重,是随着时间递减的。 一般来说,超过5年以上的数据,都是可以删除掉了。 或者说超过三年的数据,就需要压缩保存了,这样可以省掉很多磁盘空…...

Cesium教程01_认识View

Cesium 地图视图组件 目录 一、引言二、功能说明三、代码实现 1. 模板结构2. 脚本逻辑3. 样式设计 四、总结 一、引言 在三维地球可视化中&#xff0c;Cesium 是一个强大的开源 JavaScript 库&#xff0c;它能够展示精美的地球和地图应用。本示例展示了如何使用 Vue 组件化…...

【SQL Server】华中农业大学空间数据库实验报告 实验八 存储过程

1.实验目的 通过实验课程与理论课的学习深入理解掌握的存储过程的原理、创建、修改、删除、基本的使用方法、主要用途&#xff0c;并且可以在练习的基础上&#xff0c;熟练使用存储过程来进行数据库的应用程序的设计&#xff1b;深入学习深刻理解与存储过程相关的T-SQL语句的编…...

ArcMap 处理栅格数据的分辨率功能操作

ArcMap 处理栅格数据的分辨率功能操作 一、统一多分辨率栅格数据 1、查看两个栅格数据的分辨率 1&#xff09;raster1 点击属性 2) raster2 2、统一像元大小 1&#xff09;点击环境 展示和填写 处理范围 栅格分析 点击确定 3、重采样 让raster1和..2保持一致&#xff0c;即…...

redis7.x源码分析:(4) ae事件处理器(一)

ae模块是redis实现的Reactor模型的封装。它的主要代码实现集中在 ae.c 中&#xff0c;另外还提供了平台相关的io多路复用的封装&#xff0c;它们都实现了一套相同的poll接口&#xff0c;就类似于C中提供了一个接口基类&#xff0c;由针对不同平台的派生类去实现。 // 创建平台…...

【React】React Router:深入理解前端路由的工作原理

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 React Router&#xff1a;深入理解前端路由的工作原理路由的演进历程传统多页面…...

51单片机-独立按键与数码管联动

独立键盘和矩阵键盘检测原理及实现 键盘的分类&#xff1a;编码键盘和非编码键盘 键盘上闭合键的识别由专用的硬件编码器实现&#xff0c;并产生键编码号或键值的称为编码键盘&#xff0c;如&#xff1a;计算机键盘。靠软件编程识别的称为非编码键盘&#xff1b;在单片机组成…...

visual studio 2005的MFC各种线程函数之间的调用关系

在 Visual Studio 2005 的 MFC 程序中的函数和消息机制涉及线程间通信、消息处理以及与窗口消息的交互。接下来我将详细分析以下每个函数的作用、如何使用它们以及它们之间的调用关系。 1. PostThreadMessage(m_iThOpID, MSG_OP_OVER, 0, (LPARAM)iLparm); 函数用途&#xff1…...

网页中调用系统的EXE文件,如打开QQ

遇到一个实际的问题&#xff0c;需要在网页中打开本地的某个工业软件。 通过点击exe文件就可以调用到程序。 比如双击qq的exe就可以启动qq的程序。 那么问题就变成了如何加载exe程序呢&#xff1f; 可以通过Java的 Process process Runtime.getRuntime().exec(command);通过…...

【单点知识】基于PyTorch讲解自动编码器(Autoencoder)

文章目录 0. 前言1. 自动编码器的基本概念1.1 定义1.2 目标1.3 结构 2. PyTorch实现自动编码器2.1 导入必要的库2.2 定义自动编码器模型2.3 加载数据2.4 训练自动编码器 3. 自动编码器的意义4. 自动编码器的应用4.1 图像处理4.2自然语言处理&#xff1a;4.3推荐系统&#xff1a…...

Halo 正式开源: 使用可穿戴设备进行开源健康追踪

在飞速发展的可穿戴技术领域&#xff0c;我们正处于一个十字路口——市场上充斥着各式时尚、功能丰富的设备&#xff0c;声称能够彻底改变我们对健康和健身的方式。 然而&#xff0c;在这些光鲜的外观和营销宣传背后&#xff0c;隐藏着一个令人担忧的现实&#xff1a;大多数这些…...

summernote富文本批量上传音频,视频等附件

普通项目,HTML的summernote富文本批量上传音频,视频等附件(其他附件同理) JS和CSS的引入 <head><th:block th:include"include :: summernote-css" /> </head> <body><th:block th:include"include :: summernote-js" /> …...

IDEA如何设置编码格式,字符编码,全局编码和项目编码格式

前言 大家好&#xff0c;我是小徐啊。我们在开发Java项目&#xff08;Springboot&#xff09;的时候&#xff0c;一般都是会设置好对应的编码格式的。如果设置的不恰当&#xff0c;容易造成乱码的问题&#xff0c;这是要避免的。今天&#xff0c;小徐就来介绍下我们如何在IDEA…...

【计算机网络实验】之静态路由配置

【计算机网络实验】之静态路由配置 实验题目实验目的实验任务实验设备实验环境实验步骤路由器配置设置静态路由测试路由器之间的连通性配置主机PC的IP测试 实验题目 静态路由协议的配置 实验目的 熟悉路由器工作原理和机制&#xff1b;巩固静态路由理论&#xff1b;设计简单…...

十五届蓝桥杯赛题-c/c++ 大学b组

握手问题 很简单&#xff0c;相互牵手即可&#xff0c;但是要注意&#xff0c;第一个人只能与其他49个人牵手&#xff0c;所以开头是加上49 #include <iostream> using namespace std; int main() {int cnt0;for(int i49;i>7;i--){cnti;//cout<<i<<&quo…...

基础自动化系统的任务

基础自动化系统的任务主要包括实现自动控制、提高生产效率、减少人工干预等。以下是其具体任务的相关介绍&#xff1a; 实现自动控制 控制机器设备&#xff1a;基础自动化系统通过预设的程序和逻辑规则&#xff0c;对机器或设备进行自动控制和运行。执行特定任务&#xff1a;这…...