当前位置: 首页 > 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;深入理解前端路由的工作原理路由的演进历程传统多页面…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...