JUC下的BlockingQueue详解

news2024/11/16 22:41:33

BlockingQueue是Java并发包(java.util.concurrent)中提供的一个接口,它扩展了Queue接口,增加了阻塞功能。这意味着当队列满时尝试入队操作,或者队列空时尝试出队操作,线程会进入等待状态,直到队列状态允许操作继续。这种设计模式有效地解决了生产者-消费者问题,确保了线程间的协作和同步,避免了忙等待,提高了系统的效率和响应性。

详细介绍

BlockingQueue是Java并发编程中处理线程间数据传递的重要工具,通过阻塞机制简化了同步控制,提高了代码的可读性和可靠性。不同的实现提供了多样化的选择,满足不同应用场景的需求。理解和熟练运用BlockingQueue,是进行高效并发编程的关键之一。

BlockingQueue是Java并发包(java.util.concurrent)中的一个重要接口,它是Queue接口的一个扩展,特别为线程间的协作而设计。它提供了一系列阻塞操作,使得在多线程环境下进行数据的生产和消费变得既安全又高效,是实现生产者-消费者模型的理想工具。

核心特点
  1. 阻塞特性:当队列为空时,试图从队列中取元素的操作会被阻塞;当队列满时,试图向队列中添加元素的操作也会被阻塞。这样设计避免了使用传统同步机制(如wait()notify())的复杂性,使得代码更加简洁且易于理解。

  2. 线程安全:所有BlockingQueue的实现都是线程安全的,多个生产者线程和消费者线程可以安全地并发访问同一个BlockingQueue实例,而不需要额外的同步控制。

  3. 公平性:某些BlockingQueue的实现允许设置公平性策略,即按照线程等待的顺序分配访问权,这有助于避免饥饿现象。

  4. 灵活的实现:Java提供了多种BlockingQueue的实现,如ArrayBlockingQueue(基于数组的固定大小队列)、LinkedBlockingQueue(基于链表的可选固定大小队列)、PriorityBlockingQueue(支持优先级排序的无界队列)和SynchronousQueue(直接交换,没有容量的队列)等,开发者可以根据具体需求选择合适的实现。

常用方法
  • put(E e):将元素添加到队列中,如果队列已满,则阻塞直到有空间可用。
  • take():从队列中移除并返回头部元素,如果队列为空,则阻塞直到有元素可用。
  • offer(E e, long timeout, TimeUnit unit):尝试在指定时间内将元素加入队列,如果队列满则等待指定时间,超时后返回false
  • poll(long timeout, TimeUnit unit):尝试在指定时间内从队列中取出一个元素,如果队列为空则等待指定时间,超时后返回null
  • drainTo(Collection<? super E> c):尽可能多地将队列中的元素转移到指定集合中,返回转移的元素数量。
适用场景
  • 生产者-消费者模式:一个或多个生产者线程负责生产数据并将其放入队列,同时一个或多个消费者线程从队列中取出数据进行处理。
  • 任务队列:在实现线程池时,可以使用BlockingQueue来存放待处理的任务,线程池中的工作线程从队列中取出任务并执行。
  • 流控和缓冲:在需要控制数据流速率或缓冲区大小的场景中,使用有界BlockingQueue可以有效防止资源耗尽。
实现原理简析

        以ArrayBlockingQueue为例,它内部维护了一个定长数组作为存储容器,并使用两个ReentrantLock(分别用于入队和出队操作)和两个Condition(用于通知等待的线程)来实现线程的阻塞和唤醒逻辑。当队列为空或满时,试图执行相应操作的线程会被挂起,等待条件满足后再被唤醒。

使用场景

        Java的BlockingQueue接口及其实现类在多线程编程中扮演着重要角色,特别是在需要协调多个线程之间数据传递的场景中。以下是几个典型的使用场景,展示了BlockingQueue的实用性和灵活性:

1. 生产者-消费者模式

这是BlockingQueue最经典的使用场景。一个或多个生产者线程负责生成数据并将其放入队列,同时一个或多个消费者线程从队列中取出数据进行处理。BlockingQueue的阻塞特性完美地解决了生产速度与消费速度不一致的问题,当队列为空时,消费者线程会阻塞等待,反之生产者线程在队列满时也会阻塞,从而实现自动的流量控制和线程同步。

2. 线程池任务调度

在实现自定义线程池或使用Executors框架时,BlockingQueue常被用作任务队列。工作线程从队列中获取任务执行,而新的任务则被提交到队列中。通过选择不同类型的BlockingQueue(如无界队列LinkedBlockingQueue或有界队列ArrayBlockingQueue),可以控制任务的接纳策略和线程池的响应性。有界队列尤其适用于限制线程池的最大工作负载,防止资源耗尽。

3. 消息队列与事件处理

在需要异步处理消息或事件的系统中,BlockingQueue可以充当简单的消息队列。事件生产者将事件放入队列,而事件处理器从队列中取出并处理这些事件。这种模式有利于解耦生产者和消费者,提高系统的可扩展性和响应性。

4. 批量处理

在需要批量处理数据的场景下,可以利用BlockingQueue来累积一定数量的数据项,然后再一次性处理。例如,数据库批量插入操作,可以先将数据放入队列,当队列达到一定大小后,再一次性执行插入操作,从而减少数据库交互次数,提高效率。

5. 限流与平滑处理

在高并发系统中,BlockingQueue可以用作一种简单的限流手段。通过设置队列的大小上限,可以限制系统在短时间内处理请求的数量,避免瞬时压力过大导致系统崩溃。同时,它还能帮助平滑处理请求,即使面对请求波峰,也能保持服务的稳定性。

6. 同步点

在需要多个线程同步的场景中,BlockingQueue可以作为一个同步点。例如,一个线程需要等待多个线程完成各自的任务后才能继续执行,可以使用BlockingQueue作为信号机制,每个任务完成时向队列中放入一个标记,主线程等待特定数量的标记后继续执行。

实际开发中的使用详情

在实际开发中,使用BlockingQueue能够显著提升代码的并发性能和线程安全,以下是具体使用详情的展开讲解,包括选择合适的队列类型、编写示例代码、异常处理、以及一些最佳实践。

1. 选择合适的队列类型

Java标准库提供了多种BlockingQueue的实现,选择合适的类型对于优化系统性能至关重要:

  • ArrayBlockingQueue:基于数组的有界阻塞队列,适合于固定大小的队列,且对内存使用有严格要求的场景。
  • LinkedBlockingQueue:基于链表的队列,可以是有界也可以是无界的(默认无界)。无界队列在生产者速度远大于消费者速度时可能造成内存溢出。
  • PriorityBlockingQueue:一个无界优先级队列,元素按照自然排序或提供的比较器排序。适用于需要按优先级处理任务的场景。
  • DelayQueue:一个无界延迟队列,元素只有在延迟期满后才能被取出。适用于定时任务调度。
  • SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作。适合一对一的线程间传递。
2. 线程的创建与任务分配
  • 使用Thread类或ExecutorService框架来创建生产者和消费者线程。推荐使用ExecutorService,因为它提供了更高级的线程管理功能,如线程池的复用、任务调度和线程生命周期管理。
3. 数据生产和消费
  • 生产者:使用put()offer()方法向队列中添加元素。put()在队列满时会阻塞直到有空间,而offer()可以在指定时间内尝试插入,超时则返回false
  • 消费者:使用take()poll()方法从队列中取出元素。take()在队列空时会阻塞直到有元素,而poll()可以在指定时间内尝试取出,超时则返回null
4. 编写示例代码
使用BlockingQueue实现生产者-消费者模式

下面的示例展示了如何使用ArrayBlockingQueue实现一个简单的生产者-消费者模型。在这个模型中,生产者不断地生成随机数并放入队列,而消费者从队列中取出数据并打印出来。这个过程展示了BlockingQueue如何有效地管理线程间的协作和同步,确保数据的生产和消费是线程安全的。

import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ProducerConsumerExample {

    public static void main(String[] args) {
        // 创建一个容量为10的阻塞队列
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);

        // 创建生产者线程
        Thread producer = new Thread(new Producer(queue));
        // 创建消费者线程
        Thread consumer = new Thread(new Consumer(queue));

        // 启动线程
        producer.start();
        consumer.start();
    }

    static class Producer implements Runnable {
        private final BlockingQueue<Integer> queue;

        public Producer(BlockingQueue<Integer> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                Random random = new Random();
                while (true) {
                    // 生成随机数
                    int number = random.nextInt(100);
                    // 将数据放入队列,如果队列满,则阻塞等待
                    queue.put(number);
                    System.out.println("Produced: " + number);
                    Thread.sleep(1000); // 模拟生产间隔
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    static class Consumer implements Runnable {
        private final BlockingQueue<Integer> queue;

        public Consumer(BlockingQueue<Integer> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    // 从队列中取出数据,如果队列空,则阻塞等待
                    int number = queue.take();
                    System.out.println("Consumed: " + number);
                    Thread.sleep(1500); // 模拟消费间隔
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
解释说明
  • 该示例中,ProducerConsumer类分别实现了Runnable接口,代表生产者和消费者线程。
  • BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); 创建了一个容量为10的ArrayBlockingQueue,即最多可以存储10个元素。
  • 生产者线程通过queue.put(number);方法尝试将生成的随机数放入队列,如果队列已满,该方法将阻塞,直到有空间可用。
  • 消费者线程通过queue.take();方法从队列中取出一个元素,如果队列为空,该方法将阻塞,直到有元素可取。
  • Thread.sleep(1000)Thread.sleep(1500)分别模拟了生产者和消费者的处理时间,让示例更加真实。
  • 通过InterruptedException的捕获和处理,确保了线程在被中断时能够正确清理资源。

这个例子展示了如何利用BlockingQueue实现线程间的高效协作,以及如何处理线程的同步和异常情况。

5. 异常处理

        在使用BlockingQueue时,需要注意处理InterruptedException。通常,当线程在等待队列的puttake操作时被中断,会抛出此异常。最佳做法是捕获该异常,并重新设置中断标志,以便上层调用者能感知到中断事件。

  • InterruptedException:当阻塞操作被中断时抛出,处理时应重新设置中断标志,例如通过Thread.currentThread().interrupt();
  • 其他异常:如NullPointerException,确保放入队列的元素非空,避免抛出异常。
6. 最佳实践
  • 合理设置队列容量:对于有界队列,合理设置队列大小可以有效防止内存溢出和过度缓冲,同时影响系统的吞吐量和响应时间。
  • 考虑公平性:某些队列实现允许设置是否公平访问,公平策略可以减少“饥饿”现象,但可能降低整体吞吐量。
  • 资源清理:确保在不再使用队列时正确关闭相关资源,尤其是在使用带资源的队列时(尽管大多数BlockingQueue实现不需要显式关闭)。
  • 监控与调试:利用Java的并发工具和日志记录,监控队列的状态(如队列长度、等待线程数等),有助于发现和解决问题。
7. 性能优化与监控
  • 容量选择:合理设置队列容量,避免队列频繁满或空导致的线程频繁阻塞和唤醒。
  • 公平性设置:对于高竞争的场景,考虑使用公平策略减少线程饥饿现象,但需注意公平策略可能降低吞吐量。
  • 监控:使用JMX监控队列状态,比如队列长度、线程等待情况,有助于及时发现并解决问题。

注意事项

        在使用Java的BlockingQueue时,为了确保代码的健壮性、性能和正确性,有几个关键的注意事项需要遵循:

1. 容量选择
  • 有界队列与无界队列:选择有界还是无界队列需根据实际需求。有界队列能够防止资源耗尽,限制系统负载,但需要合理设置队列大小,过大可能导致响应时间增加,过小则可能频繁阻塞生产者线程。无界队列则需谨慎使用,因为它可能因生产速度过快而耗尽系统资源。
2. 线程中断
  • 处理中断:在调用put()take()等可能阻塞的方法时,应妥善处理InterruptedException。当线程被中断时,应尊重中断状态,通常通过重新设置中断标志(Thread.currentThread().interrupt();)并优雅地退出循环或方法。
3. 公平性设置
  • 公平性策略:某些BlockingQueue(如ArrayBlockingQueue)允许设置公平性,公平策略可以减少饥饿现象,保证等待时间最长的线程优先获得服务,但这可能会牺牲一些吞吐量。根据应用场景权衡是否开启公平性。
4. 资源管理
  • 内存管理:特别是对于存储大型对象或大量对象的队列,要注意队列的内存占用,避免内存泄漏或内存溢出。
  • 关闭和清理:虽然BlockingQueue自身不需要显式关闭,但与其相关的资源(如在队列中传递的数据库连接、文件流等)需要妥善关闭。
5. 同步与并发控制
  • 单一职责:尽量确保每个线程只做生产或消费一件事,避免在同一个线程中同时执行生产与消费操作,这有助于提高代码的清晰度和可维护性。
  • 避免外部同步BlockingQueue内部已经实现了必要的同步,通常情况下不需要外部额外的锁。过度同步可能导致死锁或其他并发问题。
6. 性能监控
  • 监控队列状态:使用JMX或其他监控工具定期检查队列的长度、等待线程数等指标,可以帮助及时发现和解决性能瓶颈。
  • 负载平衡:在多消费者或多生产者的场景下,注意负载均衡,避免某些线程过载或空闲。
7. 测试与调试
  • 单元测试:编写充分的单元测试,模拟不同的生产者-消费者场景,包括高并发、边界条件(如队列满/空)等,确保代码的健壮性。
  • 日志记录:适当添加日志记录,尤其是在关键的生产、消费和阻塞点,有助于问题定位和性能分析。

遵循上述注意事项,可以确保在实际项目中高效、安全地使用BlockingQueue,充分发挥其在多线程编程中的优势。

优缺点

优点
  1. 线程安全与同步BlockingQueue是线程安全的,它自动管理了多线程环境下的同步问题,开发者无需编写额外的同步代码,大大降低了编程复杂度和出错概率。

  2. 生产者-消费者模式的天然实现:它完美地支持了生产者-消费者模式,通过阻塞和唤醒机制,实现了生产者和消费者线程之间的高效协调,无需担心竞态条件。

  3. 灵活的阻塞策略:提供了阻塞和非阻塞两种操作方式(如put()offer()take()poll()),可以根据具体需求选择最适合的策略。

  4. 流量控制:尤其是有界队列,可以作为天然的流量控制工具,限制系统处理任务的速度,防止资源耗尽或过载。

  5. 提高系统吞吐量:通过减少线程间的直接同步,减少上下文切换,提高了并发处理的效率和系统整体吞吐量。

  6. 丰富的实现选择:Java并发包提供了多种BlockingQueue的实现,如ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueue等,满足不同场景下的需求。

缺点
  1. 潜在的阻塞风险:特别是在使用无界队列或不恰当的队列大小时,如果生产速度远大于消费速度,可能导致队列无限增长,最终耗尽系统资源。

  2. 性能开销:虽然自动的同步和阻塞机制简化了编程,但是相比直接的线程间传递,使用BlockingQueue会有一定的性能开销,尤其是在高并发、低延迟的应用中。

  3. 调试困难:由于线程的阻塞和唤醒机制,当程序出现死锁或性能问题时,调试和问题定位可能较为复杂。

  4. 公平性与吞吐量的权衡:启用公平策略可以减少线程饥饿,但可能牺牲系统吞吐量。选择合适的队列和策略需要基于对应用特性的深入理解。

  5. 功能局限性:尽管BlockingQueue非常强大,但它主要是为了解决线程间的数据传递问题,对于更复杂的并发控制或数据处理逻辑,可能需要配合其他并发工具一起使用。

BlockingQueue是Java并发编程中处理线程间通信的强大工具,但在使用时需根据具体场景合理选择队列类型,正确处理阻塞和中断,以避免潜在的性能问题和资源耗尽风险。

可能遇到的问题及解决方案

在使用上述Java生产者-消费者模型时,可能会遇到一些常见问题,下面我将详细展开这些问题及其解决方案:

1. 死锁

问题描述:如果生产者和消费者在不恰当的时机互相等待对方,可能导致整个程序陷入死锁状态。

解决方案

  • 确保puttake操作的使用不会导致循环等待。在这个示例中,因为仅使用了BlockingQueue进行同步,且没有其他复杂的锁依赖,所以直接使用标准库的阻塞队列通常能避免死锁问题。
  • 避免在持有锁的情况下调用可能阻塞的方法,除非你确切知道这样做不会引发死锁。
2. 资源泄露

问题描述:未正确处理线程中断可能导致资源无法释放,如线程池未关闭等。

解决方案

  • 在捕获到InterruptedException时,通过调用Thread.currentThread().interrupt();重新设置中断标志,确保上层调用者能感知到中断事件。
  • 使用完毕后,如果适用,确保关闭相关的资源,比如关闭文件流、数据库连接等。
3. 饿死问题

问题描述:如果生产者速度远大于消费者,或者反之,可能导致一方始终无法获取到资源,出现“饿死”现象。

解决方案

  • 采用动态调整策略,比如根据队列的当前长度调整生产或消费的速度。
  • 可以考虑使用优先级队列(PriorityBlockingQueue),虽然在这个场景中可能不直接适用,但在某些情况下能帮助解决特定类型的饿死问题。
4. 性能瓶颈

问题描述:高并发环境下,队列成为瓶颈,影响整体吞吐量。

解决方案

  • 考虑使用更高效的并发队列,如ConcurrentLinkedQueue(非阻塞),适合高并发但不严格要求顺序的场景。
  • 调整队列容量,根据实际需求和系统资源合理设定。
  • 分析并优化生产者和消费者的逻辑,减少不必要的计算和等待时间。
5. 数据一致性问题

问题描述:在多线程环境下,除了队列本身的同步问题外,生产者或消费者内部的状态变更也可能导致一致性问题。

解决方案

  • 使用同步机制(如synchronized块、ReentrantLock)保护共享资源的访问。
  • 应用原子操作(AtomicInteger等)减少对锁的依赖,提高并发性能。
  • 确保对队列的操作是原子的,并且在必要时使用更高级的并发工具类,如CountDownLatchCyclicBarrier进行更复杂的同步控制。

生产者-消费者模式在实现过程中需要细心考虑线程间的协调与同步,以及潜在的性能和可靠性问题。通过合理的队列选择、适当的同步机制、以及对系统负载的合理评估和调整,可以有效避免上述问题,构建出稳定高效的并发系统。

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

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

相关文章

【Python系列】Python中列表属性提取

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

AI菜鸟向前飞 — 大模型基础知识篇

前言 主要介绍最最基础的知识&#xff0c;在这个基础上有现在比较流行的GPT、Llama、Gemini**等一系列大模型的出现&#xff0c;打好基础才能更理解上面是如何运作以及实现的过程。 PS&#xff1a;本篇科普不会介绍梯度下降算法&#xff08;偏导数&#xff09;等复杂的过程&a…

巨坑啊! before-upload返回false 会执行on-remove

通过对on-remove对应参数的打印&#xff0c;发现回调中的file参数有个status&#xff0c;若是是在before-upload中就被过滤了&#xff0c;就是ready&#xff0c;若是已经上传成功了去点击删除&#xff0c;status是success&#xff0c;就他了。 onRemove(file,fileList){if(file…

redis的双写一致性

双写一致性问题 1.先删除缓存或者先修改数据库都可能出现脏数据。 2.删除两次缓存&#xff0c;可以在一定程度上降低脏数据的出现。 3.延时是因为数据库一般采用主从分离&#xff0c;读写分离。延迟一会是让主节点把数据同步到从节点。 1.读写锁保证数据的强一致性 因为一般放…

Redis——缓存雪崩、缓存穿透、缓存击穿

在项目中&#xff0c;通常会使用数据库比如 MySQL 存储应用数据&#xff0c;但是当数据太多之后&#xff0c;比如多了几十万条或上百万条的商品信息&#xff0c;这个时候查询商品数据的速度会很慢&#xff0c;影响用户体验。此时一般我们会选择将部分商品信息缓存起来&#xff…

【Rollup】用rollup从0到1开发一个js插件并发布到npm

Rollup 是一个 JavaScript 模块打包器&#xff0c;专注于打包 ES6 模块将其编译回多种模块化格式&#xff0c;尤其适合打包库和框架&#xff0c;因为它可以生成更小、更高效的代码&#xff0c;并且特别适合将代码打包成可在浏览器中使用的库。 从0到1开发js插件 1.创建文件夹…

2.前端路由的配置和使用

一&#xff0c;路由的作用 路由的作用就是将页面文件跟URL地址形成对应匹配 二&#xff0c;如何安装路由 这里我们采用pnpm的方式在项目中执行 pnpm install vue-routernext --save三&#xff0c;路由如何使用 首先创建一个我们需要访问的页面文件&#xff0c;这里我先创建…

机器学习:葡萄酒品质预测

说明&#xff0c;此项目是我的期末大作业&#xff0c;包括了对数据集探索&#xff0c;预处理以及分类的各个详细过程与描述&#xff0c;代码简单&#xff0c;主要是一个分类项目的流程&#xff0c;并没有对模型进行深度研究&#xff0c;因此我写在这里。 目录 一、问题介绍 …

ETL工具kettle(PDI)入门教程,Job

先新建两个Transform&#xff0c;MysqlToMysql.ktr和CsvToExcel.ktrURL&#xff1a;ETL工具kettle入门教程&#xff0c;transform&#xff0c;Mysql-&#xff1e;Mysql&#xff0c;Csv-&#xff1e;Excel-CSDN博客 主对象树&#xff0c;作业上右击&#xff0c;点击新建 核心对…

Unity引擎是什么?有哪些优点

大家好&#xff0c;我是咕噜土豆&#xff0c;很高兴又和大家见面了。今天我们一起来了解一下Unity引擎和它有哪些优点。 首先带大家了解什么是Unity引擎 Unity引擎是一款由Unity Technologies开发的跨平台游戏开发引擎&#xff0c;广泛用于创建2D和3D游戏以及其他交互式内容&…

uniapp管理后台编写,基于uniadmin和vue3实现uniapp小程序的管理后台

一&#xff0c;创建uniAdmin项目 打开开发者工具Hbuilder,然后点击左上角的文件&#xff0c;点新建&#xff0c;点项目。如下图。 选择uniadmin&#xff0c;编写项目名&#xff0c;然后使用vue3 记得选用阿里云服务器&#xff0c;因为最便宜 点击创建&#xff0c;等待项目创…

AI跟踪报道第41期-新加坡内哥谈技术-本周AI新闻:本周Al新闻: 准备好了吗?事情即将変得瘋狂

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

在Spring Boot应用安装SSL证书

目录 前提条件 步骤一&#xff1a;下载SSL证书 步骤二&#xff1a;在Spring Boot安装SSL证书 步骤三&#xff1a;验证SSL证书是否安装成功 前提条件 已通过数字证书管理服务控制台签发证书SSL证书绑定的域名已完成DNS解析&#xff0c;即您的域名与主机IP地址相互映射已在W…

ASP.NET学生信息管理系统

摘 要 本文介绍了在ASP.net环境下采用“自上而下地总体规划&#xff0c;自下而上地应用开发”的策略开发一个管理信息系统的过程。通过分析某一学校学生管理的不足&#xff0c;创建了一套行之有效的计算机管理学生的方案。文章介绍了学生管理信息系统的系统分析部分&#xff0c…

【C++ | 函数】默认参数、哑元参数、函数重载、内联函数

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a;2024-05-04 1…

Linux —— 信号(4)

Linux —— 信号&#xff08;4&#xff09; 信号的处理用户态和内核态 信号的捕捉sigaction sa_mask字段volatileSIGCHLD信号 我们今天接着来看信号&#xff1a; 信号的处理 信号的处理简单一句话就是在内核态处理的。 用户态和内核态 用户态和内核态是操作系统和计组中的概…

MySQL系列之索引

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 往期热门专栏回顾 专栏…

【声呐仿真】学习记录2-运行ROV(带camera、sonar、dvl等传感器)例程

【声呐仿真】学习记录2-运行ROV&#xff08;带camera、sonar、dvl等传感器&#xff09;例程 前言第一阶段-学习Gazebo第二阶段-学习URDF、xacro第三阶段-寻找例程跑一个rexrov示例程序1.uuvsimulator quick_start2.能键盘控制的示例程序&#xff08;失败&#xff09;3.能键盘控…

[初阶数据结构】单链表

前言 &#x1f4da;作者简介&#xff1a;爱编程的小马&#xff0c;正在学习C/C&#xff0c;Linux及MySQL。 &#x1f4da;本文收录于初阶数据结构系列&#xff0c;本专栏主要是针对时间、空间复杂度&#xff0c;顺序表和链表、栈和队列、二叉树以及各类排序算法&#xff0c;持…

【Python系列】字节串与字典字节串

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…