JUC高并发编程10:线程池

1 线程池概述

1.1 线程池简介

线程池(Thread Pool)是一种线程使用模式。在多线程编程中,线程的创建和销毁会带来一定的开销,尤其是在处理大量短时间任务时,频繁的线程创建和销毁会导致调度开销增加,进而影响缓存局部性和整体性能。线程池通过维护一组线程,等待监督管理者分配可并发执行的任务,从而避免了频繁创建和销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

1.2 线程池的优势

线程池的主要工作是控制运行的线程数量。在处理任务时,线程池会将任务放入队列中,然后在线程创建后启动这些任务。如果线程数量超过了预设的最大数量,超出数量的线程会排队等候,直到有其他线程执行完毕,再从队列中取出任务来执行。

1.3 线程池特点

  1. 降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  2. 提高响应速度:当任务到达时,任务可以立即执行,而不需要等待线程的创建。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

1.4 线程池架构

在 Java 中,线程池是通过 Executor 框架实现的。该框架中涉及以下几个核心类和接口:

  1. Executor:这是一个接口,定义了执行任务的基本方法 execute(Runnable command)
  2. Executors:这是一个工具类,提供了创建不同类型线程池的静态方法,如 newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutor 等。
  3. ExecutorService:这是 Executor 的子接口,扩展了 Executor 的功能,提供了管理线程池的方法,如 submitshutdownshutdownNow 等。
  4. ThreadPoolExecutor:这是 ExecutorService 接口的实现类,提供了线程池的具体实现,可以自定义线程池的参数,如核心线程数、最大线程数、任务队列等。

2 线程池的使用方式

在 Java 中,线程池的使用可以通过 Executors 工具类提供的静态方法来创建不同类型的线程池。以下是几种常见的线程池使用方式:

2.1 一池N线程

Executors.newFixedThreadPool(int nThreads)

  • 描述:创建一个固定大小的线程池,线程池中的线程数量固定为 nThreads。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入任务队列中等待执行。
  • 适用场景:适用于任务量比较稳定,且任务执行时间较长的场景。
public class ThreadPoolDemo1 {public static void main(String[] args) {// 一池五线程 5个窗口ExecutorService threadPool1 = Executors.newFixedThreadPool(5);// 10个顾客请求try {for (int i = 1; i <= 10; i++) {// 执行threadPool1.execute(()->{System.out.println(Thread.currentThread().getName()+ " 办理业务");});}} catch (Exception e) {throw new RuntimeException(e);} finally {// 关闭threadPool1.shutdown();}}
}

2.2 一个任务一个任务执行,一池一线程

Executors.newSingleThreadExecutor()

  • 描述:创建一个只有一个线程的线程池。所有任务都会按照提交的顺序依次执行,保证任务的执行顺序。
  • 适用场景:适用于需要保证任务顺序执行的场景,如日志记录、文件操作等。
public class ThreadPoolDemo1 {public static void main(String[] args) {// 一池一线程 1个窗口ExecutorService threadPool2= Executors.newSingleThreadExecutor();// 10个顾客请求try {for (int i = 1; i <= 10; i++) {// 执行threadPool2.execute(()->{System.out.println(Thread.currentThread().getName()+ " 办理业务");});}} catch (Exception e) {throw new RuntimeException(e);} finally {// 关闭threadPool2.shutdown();}}
}

2.3 线程池根据需求创建线程,可扩容,遇强则强

Executors.newCachedThreadPool()

  • 描述:创建一个可缓存的线程池。线程池会根据任务的数量动态调整线程的数量,如果当前没有空闲线程,则会创建新的线程;如果线程空闲时间超过60秒,则会被回收。
  • 适用场景:适用于任务量不稳定,且任务执行时间较短的场景。
public class ThreadPoolDemo1 {public static void main(String[] args) {// 一池可扩容线程ExecutorService threadPool3 = Executors.newCachedThreadPool();// 10个顾客请求try {for (int i = 1; i <= 10; i++) {// 执行threadPool3.execute(()->{System.out.println(Thread.currentThread().getName()+ " 办理业务");});}} catch (Exception e) {throw new RuntimeException(e);} finally {// 关闭threadPool3.shutdown();}}
}

2.4 定时以及周期性执行任务

Executors.newScheduledThreadPool(int corePoolSize)

  • 描述:创建一个可以定时以及周期性执行任务的线程池。线程池中的线程数量固定为 corePoolSize,可以延迟执行任务或按照固定周期执行任务。
  • 适用场景:适用于需要定时或周期性执行任务的场景,如定时任务调度、周期性数据同步等。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool.scheduleAtFixedRate(() -> {System.out.println(Thread.currentThread().getName() + " is running");
}, 0, 1, TimeUnit.SECONDS); // 每秒执行一次

2.5 拥有多个任务队列

Executors.newWorkStealingPool()

  • 描述:jdk1.8 提供的线程池,底层使用的是 ForkJoinPool 实现,创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用 cpu 核数的线程来并行执行任务。
  • 适用场景:适用于任务量较大且任务执行时间不均匀的场景,可以提高任务的并行度和执行效率。
ExecutorService workStealingPool = Executors.newWorkStealingPool();
for (int i = 0; i < 10; i++) {workStealingPool.execute(() -> {System.out.println(Thread.currentThread().getName() + " is running");});
}
workStealingPool.shutdown();

2.6 小结

  • newFixedThreadPool:适用于任务量稳定,且任务执行时间较长的场景。
  • newSingleThreadExecutor:适用于需要保证任务顺序执行的场景。
  • newCachedThreadPool:适用于任务量不稳定,且任务执行时间较短的场景。
  • newScheduledThreadPool:适用于需要定时或周期性执行任务的场景。
  • newWorkStealingPool:适用于任务量较大且任务执行时间不均匀的场景,可以提高任务的并行度和执行效率。

3 线程池底层原理

在 Java 中,Executors 工具类提供了几种常见的线程池创建方法,包括 newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPoolnewScheduledThreadPoolnewWorkStealingPool。这些方法的底层实现都依赖于 ThreadPoolExecutor 类或其变体,只是它们的参数配置不同。

3.1 newFixedThreadPool

Executors.newFixedThreadPool(int nThreads)

实现原理

  • 核心线程数nThreads
  • 最大线程数nThreads
  • 任务队列LinkedBlockingQueue(无界队列)
  • 线程存活时间:0(线程不会被回收)
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

特点

  • 线程池中的线程数量固定为 nThreads
  • 任务队列是无界的 LinkedBlockingQueue,可以容纳无限多的任务。
  • 线程不会被回收,因为线程存活时间为0。

3.2 newSingleThreadExecutor

Executors.newSingleThreadExecutor()

实现原理

  • 核心线程数:1
  • 最大线程数:1
  • 任务队列LinkedBlockingQueue(无界队列)
  • 线程存活时间:0(线程不会被回收)
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

特点

  • 线程池中只有一个线程。
  • 任务队列是无界的 LinkedBlockingQueue,可以容纳无限多的任务。
  • 线程不会被回收,因为线程存活时间为0。
  • 任务会按照提交的顺序依次执行,保证任务的执行顺序。

3.3 newCachedThreadPool

Executors.newCachedThreadPool()

实现原理

  • 核心线程数:0
  • 最大线程数Integer.MAX_VALUE(理论上可以创建无限多的线程)
  • 任务队列SynchronousQueue(同步队列,不存储任务)
  • 线程存活时间:60秒(线程空闲时间超过60秒会被回收)
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

特点

  • 线程池中的线程数量不固定,根据任务的数量动态调整。
  • 任务队列是 SynchronousQueue,它不存储任务,而是直接将任务交给线程执行。
  • 线程空闲时间超过60秒会被回收,以节省系统资源。
  • 适用于任务量不稳定,且任务执行时间较短的场景。

3.4 newScheduledThreadPool

Executors.newScheduledThreadPool(int corePoolSize)

实现原理

  • 核心线程数corePoolSize
  • 最大线程数Integer.MAX_VALUE(理论上可以创建无限多的线程)
  • 任务队列DelayedWorkQueue(延迟队列)
  • 线程存活时间:0(线程不会被回收)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
}

特点

  • 线程池中的线程数量固定为 corePoolSize
  • 任务队列是 DelayedWorkQueue,用于存储延迟执行的任务。
  • 适用于需要定时或周期性执行任务的场景。

3.5 newWorkStealingPool

Executors.newWorkStealingPool()

实现原理

  • 核心线程数:取决于系统可用的处理器数量(Runtime.getRuntime().availableProcessors()
  • 最大线程数:取决于系统可用的处理器数量
  • 任务队列ForkJoinPool 的工作窃取队列
  • 线程存活时间:60秒(线程空闲时间超过60秒会被回收)
public static ExecutorService newWorkStealingPool() {return new ForkJoinPool(Runtime.getRuntime().availableProcessors(),ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);
}

特点

  • 线程池中的线程数量取决于系统可用的处理器数量。
  • 任务队列是 ForkJoinPool 的工作窃取队列,每个线程都有一个私有的任务队列。
  • 当一个线程的任务队列为空时,它会从其他线程的任务队列中窃取任务来执行。
  • 适用于任务量较大且任务执行时间不均匀的场景,可以提高任务的并行度和执行效率。

4 线程池七个参数介绍

  • int corePoolSize: 线程池的核心(常驻)线程数
  • int maximumPoolSize: 能容纳的最大线程数
  • long keepAliveTime :空闲线程存活时间
  • TimeUnit unit: 存活的时间单位
  • BlockingQueue<Runnable> workQueue :存放提交但未执行任务的队列
  • ThreadFactory threadFactory: 创建线程的工厂类
  • RejectedExecutionHandler handler: 等待队列满后的拒绝策略

5 线程池底层工作流程

线程池的工作流程可以分为任务提交、任务执行和线程回收三个主要阶段。以下是线程池的详细工作流程:

5.1 任务提交阶段

当调用 execute() 方法或 submit() 方法向线程池提交任务时,线程池会根据当前的状态和配置做出相应的判断和处理。

  • 如果正在运行的线程数量小于 corePoolSize

    • 线程池会立即创建一个新的核心线程来运行这个任务。
  • 如果正在运行的线程数量大于或等于 corePoolSize

    • 线程池会将这个任务放入任务队列(如 LinkedBlockingQueueSynchronousQueue)中等待执行。
  • 如果任务队列满了且正在运行的线程数量小于 maximumPoolSize

    • 线程池会创建一个新的非核心线程来立即运行这个任务。
  • 如果任务队列满了且正在运行的线程数量大于或等于 maximumPoolSize

    • 线程池会启动饱和拒绝策略(如 AbortPolicyCallerRunsPolicyDiscardPolicyDiscardOldestPolicy)来处理这个任务。

5.2 任务执行阶段

当一个线程完成当前任务后,它会从任务队列中取下一个任务来执行。如果任务队列为空,线程会进入等待状态,直到有新的任务被提交。

5.3 线程回收阶段

当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会进行线程回收。

  • 如果当前运行的线程数大于 corePoolSize

    • 线程池会停掉这个线程,以减少资源消耗。
  • 如果当前运行的线程数小于或等于 corePoolSize

    • 线程会继续保持活动状态,等待新的任务。

5.4 线程池的收缩

当线程池中的所有任务完成后,线程池会根据配置进行收缩。最终,线程池中的线程数量会收缩到 corePoolSize 的大小。

5.5 小结

线程池的工作流程可以概括为以下几个步骤:

  1. 任务提交:根据当前运行的线程数量和任务队列的状态,决定是创建新线程、将任务放入队列还是启动饱和拒绝策略。
  2. 任务执行:线程从任务队列中取任务并执行。
  3. 线程回收:当线程空闲时间超过 keepAliveTime 时,线程池会根据当前运行的线程数量决定是否回收线程。

6 拒绝策略

当线程池的任务队列已满且线程数量达到最大线程数时,线程池会触发拒绝策略来处理新提交的任务。Java 提供了四种内置的拒绝策略,分别是 AbortPolicyCallerRunsPolicyDiscardOldestPolicyDiscardPolicy

6.1 AbortPolicy

  • 描述:丢弃任务,并抛出 RejectedExecutionException 异常。这是线程池默认的拒绝策略。
  • 适用场景:适用于需要严格控制任务提交的场景,确保任务不会被无条件丢弃。
  • 注意事项:必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
public static class AbortPolicy implements RejectedExecutionHandler {public AbortPolicy() {}public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException("Task " + r.toString() +" rejected from " +e.toString());}
}

6.2 CallerRunsPolicy

  • 描述:当触发拒绝策略时,只要线程池没有关闭,则使用调用线程直接运行任务。
  • 适用场景:适用于并发较小、性能要求不高、不允许任务失败的场景。
  • 注意事项:由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上会有较大损失。
public static class CallerRunsPolicy implements RejectedExecutionHandler {public CallerRunsPolicy() {}public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}
}

6.3 DiscardOldestPolicy

  • 描述:当触发拒绝策略时,只要线程池没有关闭,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入。
  • 适用场景:适用于任务队列中任务的优先级不重要,可以丢弃旧任务的场景。
  • 注意事项:可能会导致一些任务被无条件丢弃,需要根据具体业务场景慎重选择。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {public DiscardOldestPolicy() {}public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {e.getQueue().poll();e.execute(r);}}
}

6.4 DiscardPolicy

  • 描述:直接丢弃任务,不进行任何处理。
  • 适用场景:适用于任务可以被无条件丢弃的场景,如日志记录等。
  • 注意事项:任务被丢弃后不会有任何通知或异常抛出,需要确保业务逻辑允许任务丢失。
public static class DiscardPolicy implements RejectedExecutionHandler {public DiscardPolicy() {}public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
}

6.5 小结

  • AbortPolicy:丢弃任务并抛出异常,适用于需要严格控制任务提交的场景。
  • CallerRunsPolicy:使用调用线程直接运行任务,适用于并发较小、性能要求不高、不允许任务失败的场景。
  • DiscardOldestPolicy:丢弃队列中最老的任务,适用于任务优先级不重要的场景。
  • DiscardPolicy:直接丢弃任务,适用于任务可以被无条件丢弃的场景。

7 自定义线程池

在实际开发中,建议避免使用 Executors 工具类创建线程池,而是通过 ThreadPoolExecutor 类来自定义线程池。这样可以更好地控制线程池的参数,避免潜在的内存溢出(OOM)问题。

7.1 为什么避免使用 Executors 创建线程池?

  • FixedThreadPoolSingleThreadPool

    • 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致内存溢出(OOM)。
  • CachedThreadPoolScheduledThreadPool

    • 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致内存溢出(OOM)。

7.2 自定义线程池的步骤

  1. 定义线程池的核心参数

    • corePoolSize:核心线程数。
    • maximumPoolSize:最大线程数。
    • keepAliveTime:线程空闲时间。
    • unit:时间单位。
    • workQueue:任务队列。
    • threadFactory:线程工厂。
    • handler:拒绝策略。
  2. 创建 ThreadPoolExecutor 实例

    • 使用上述参数创建 ThreadPoolExecutor 实例。
  3. 提交任务

    • 使用 execute()submit() 方法提交任务。

7.3 示例代码

  • 代码1
import java.util.concurrent.*;public class CustomThreadPool {public static void main(String[] args) {// 定义线程池的核心参数int corePoolSize = 5;int maximumPoolSize = 10;long keepAliveTime = 60L;TimeUnit unit = TimeUnit.SECONDS;BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);ThreadFactory threadFactory = Executors.defaultThreadFactory();RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();// 创建 ThreadPoolExecutor 实例ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);// 提交任务for (int i = 0; i < 150; i++) {threadPoolExecutor.execute(() -> {System.out.println(Thread.currentThread().getName() + " is running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池threadPoolExecutor.shutdown();}
}
  • 参数解释

    • corePoolSize:核心线程数,线程池中始终保持的线程数量。
    • maximumPoolSize:最大线程数,线程池中允许的最大线程数量。
    • keepAliveTime:线程空闲时间,当线程数量超过核心线程数时,多余的线程在空闲时间超过 keepAliveTime 后会被回收。
    • unit:时间单位,keepAliveTime 的时间单位。
    • workQueue:任务队列,用于存放待执行的任务。这里使用 LinkedBlockingQueue,并设置队列长度为100。
    • threadFactory:线程工厂,用于创建新线程。这里使用默认的线程工厂。
    • handler:拒绝策略,当任务队列已满且线程数量达到最大线程数时,新任务的处理策略。这里使用 AbortPolicy,即丢弃任务并抛出异常。
  • 代码2

public class ThreadPoolDemo2 {public static void main(String[] args) {ExecutorService threadPool = new ThreadPoolExecutor(2,5,2L,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());// 10个顾客请求try {for (int i = 1; i <= 10; i++) {// 执行threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+ " 办理业务");});}} catch (Exception e) {throw new RuntimeException(e);} finally {// 关闭threadPool.shutdown();}}
}

8 思维导图

在这里插入图片描述

9 参考链接

【【尚硅谷】大厂必备技术之JUC并发编程】

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

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

相关文章

Java 集合 Collection常考面试题

理解集合体系图 collection中 list 是有序的,set 是无序的 什么是迭代器 主要遍历 Collection 集合中的元素,所有实现了 Collection 的集合类都有一个iterator()方法,可以返回一个 iterator 的迭代器。 ArrayList 和 Vector 的区别? ArrayList 可以存放 null,底层是由数…

【算法】滑动窗口(续)

一、将x减到0的最小操作数 1658. 将 x 减到 0 的最小操作数 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums 和一个整数 x 。每一次操作时&#xff0c;你应当移除数组 nums 最左边或最右边的元素&#xff0c;然后从 x 中减去该元素的值。请注意&#xff0c;需要…

2024长城杯WP

WEB SQLUP 打开题目给了一个登录页面结合名字猜测为SQL注入 查看源码发现有hint提示开发者使用的是模式匹配 所以我尝试使用%来模糊匹配&#xff0c;登陆成功 usernameadmin&password% 进入面板之后发现有一个文件上传功能 尝试上传php文件&#xff0c;结果被waf&#xff0…

【银河麒麟高级服务器操作系统】安全配置基线相关分析全过程及解决方案

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 服务器环境以及配置 【机型】物理机或虚机 【…

SpringBoot开发——整合Admin监控服务

文章目录 1、SpringBoot-Admin简介2、SpringBoot整合Admin监控服务2.1 创建SpringBoot-Admin项目(服务端)2.1.1 创建一个SpringBoot项目2.1.2 选择相关依赖2.1.3 启用Admin监控服务2.1.4 启用项目2.2 配置需要被监听的项目(客户端)2.2.1 被监听的项目添加相关依赖2.2.2 配置被…

Redis高级篇 —— 分布式缓存

Redis高级篇 —— 分布式缓存 文章目录 Redis高级篇 —— 分布式缓存1 Redis持久化1.1 RDB1.2 RDB的fork原理1.3 RDB总结1.4 AOF持久化1.5 RDB和AOF的对比 2 Redis主从2.1 搭建主从架构2.2 数据同步原理2.2.1 全量同步2.2.2 增量同步 3 Redis哨兵3.1 哨兵的作用和原理3.1.1 哨兵…

kafka和zookeeper单机部署

安装kafka需要jdk和zookeeper环境&#xff0c;因此先部署单机zk的测试环境。 zookeeper离线安装 下载地址&#xff1a; zookeeper下载地址&#xff1a;Index of /dist/zookeeper 这里下载安装 zookeeper-3.4.6.tar.gz 版本&#xff0c;测试环境单机部署 上传服务器后解压缩 …

Python酷库之旅-第三方库Pandas(142)

目录 一、用法精讲 641、pandas.Timestamp.hour属性 641-1、语法 641-2、参数 641-3、功能 641-4、返回值 641-5、说明 641-6、用法 641-6-1、数据准备 641-6-2、代码示例 641-6-3、结果输出 642、pandas.Timestamp.is_leap_year属性 642-1、语法 642-2、参数 6…

使用Python编写你的第一个算法交易程序

背景 Background ​ 最近想学习一下量化金融&#xff0c;总算在盈透投资者教育&#xff08;IBKRCampus&#xff09;板块找到一篇比较好的算法交易入门教程。我在记录实践过程后&#xff0c;翻译成中文写成此csdn博客&#xff0c;分享给大家。 ​ 如果你的英语好可以直接看原文…

用FPGA做一个全画幅无反相机

做一个 FPGA 驱动的全画幅无反光镜数码相机是不是觉得很酷&#xff1f; 就是上图这样。 Sitina 一款开源 35 毫米全画幅 (3624 毫米) CCD 无反光镜可换镜头相机 (MILC)&#xff0c;这个项目最初的目标是打造一款数码相机&#xff0c;将 SLR [单镜头反光] 相机转换为 DSLR [数码…

SpringBoot 集成 Redis

一&#xff1a;SpringBoot 集成 Redis ①Redis是一个 NoSQL&#xff08;not only&#xff09;数据库&#xff0c; 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器&#xff1b;常用的数据类型&#xff1a; string , hash ,set ,zset , list ③通过Redis客…

初阶C语言-结构体

一.结构体的声明 1.结构体类型的声明 1.1结构的基础知识 结构是一些值的集合&#xff0c;这些值称为称为变量。结构的每个成员可以是不同类型的变量。 1.2结构的声明 struct tag //struct是结构体关键字&#xff0c;tag是结构体类型名称 { member - list;//成员变…

D26【python 接口自动化学习】- python 基础之判断与循环

day26 语句嵌套 学习日期&#xff1a;20241003 学习目标&#xff1a;判断与循环&#xfe63;-36 语句嵌套&#xff1a;如何处理多重嵌套的问题&#xff1f; 学习笔记&#xff1a; 语句嵌套的用途 在条件语句中使用另外一个条件语句 在循环中使用条件语句 多重循环 总结 1…

linux查看k8s的开机启动状态 systemctl is-enabled 查看开机启动状态

查看k8s的开机启动状态 在Kubernetes中&#xff0c;通常使用systemd来管理服务的启动。但是&#xff0c;Kubernetes节点上的服务可能不是由systemd直接管理&#xff0c;而是通过kubelet服务来管理。因此&#xff0c;检查Kubernetes节点的开机启动状态&#xff0c;你需要检查ku…

Unity网络开发 - C#开源网络通信库PESocket的使用

概述 在现代多人在线游戏中&#xff0c;稳定且高效的网络通信是确保游戏体验的关键。本文将探讨如何利用C#开源网络通信库PESocket来构建一个简单的Unity客户端与.NET控制台服务器之间的实时消息传递系统。通过本例&#xff0c;读者不仅能够了解PESocket的基本用法&#xff0c…

稀土抗紫外屏蔽剂的用途

稀土抗紫外屏蔽剂具有光、热稳定性好&#xff0c;可高效吸收/有效屏蔽280-400nm范围内的紫外线&#xff0c;无二次氧化过程的缺点&#xff0c;彻底解决产品因紫外线原因造成的变质和老化问题&#xff0c;并且具有添加量小、无毒、不易析出等优点。 稀土抗紫外屏蔽剂的用途只要有…

安全网络架构

网络安全解决方案是指通过一系列技术和措施来保护网络系统和数据的安全。它涉及多个方面&#xff0c;包括网络设备的防护、数据的加密和备份、安全策略的制定和执行等。以下是一些常见的网络安全解决方案&#xff1a; 防火墙&#xff1a;防火墙是一种硬件或软件设备&#xff0c…

qt+opengl 实现纹理贴图,平移旋转,绘制三角形,方形

1 首先qt 已经封装了opengl&#xff0c;那么我们就可以直接用了&#xff0c;这里面有三个函数需要继承 virtual void initializeGL() override; virtual void resizeGL(int w,int h) override; virtual void paintGL() override; 这三个函数是实现opengl的重要函数。 2 我们…

Leetcode 买卖股票的最佳时机

这段代码的目的是解决“买卖股票的最佳时机”这个问题&#xff0c;即在给定的股票价格数组中&#xff0c;找到一次买入和卖出所能获得的最大利润。 算法思想&#xff1a; 定义两个变量&#xff1a; minPrice: 这个变量用于记录迄今为止遇到的最小股票价格&#xff08;买入价格…

RandLA-Net 基于 Tensorflow , 训练自定义数据集

搭建 RandLA-Net 训练环境, 生成自定义训练数据集, 训练自定义数据集. Code: https://github.com/QingyongHu/RandLA-Net 搭建训练环境 Clone the repositorygit clone --depth=1 https://github.com/QingyongHu