高效并发编程:Java阻塞队列深度解析与最佳实践
1.阻塞队列的基本概念与应用场景
1.1 阻塞队列的定义
阻塞队列(BlockingQueue)是Java并发包中的一个接口,它支持两个附加操作:当队列为空时,获取元素的线程会等待队列变为非空;当队列满时,存储元素的线程会等待队列可用。这种队列通常用于生产者和消费者的场景,其中生产者不能在意想不到的速度填充队列,以至于消耗所有可用的内存资源。
public interface BlockingQueue<E> extends Queue<E> {void put(E e) throws InterruptedException;E take() throws InterruptedException;// ... 其他方法省略
}
1.2 阻塞队列的主要用途
阻塞队列最典型的案例就是生产者-消费者模型,在这种模型中,生产者将对象放入队列,消费者则从队列中取出这些对象。使用阻塞队列可以有效地协调生产者和消费者之间的速度,如果生产者比消费者快,队列会满,这会让生产者在尝试放入元素时阻塞。如果消费者比生产者快,队列会空,这会让消费者在尝试取出元素时阻塞。
除了平衡生产与消费的节奏外,阻塞队列还在很多异步处理的场景中发挥作用,如并行计算、消息处理系统等。
2.阻塞队列的核心方法解析
2.1 插入方法
put(E e): 一个用于插入元素的方法,如果队列满了,它将等待空间变得可用。
blockingQueue.put("Value");
2.2 移除方法
take(): 用于移除并返回队列头部的元素,如果队列为空,它将等待直至元素变得可用。
String value = blockingQueue.take();
2.3 检查方法
peek(): 用于检查队列头部的元素而不移除,此方法不会阻塞。
String nextValue = blockingQueue.peek();
2.4 阻塞与超时处理机制
阻塞队列提供了超时机制的方法如 offer() 和 poll(),允许定义一个等待的时间,这为防止因无休止的等待而使程序无法继续执行提供了解决方案。
boolean success = blockingQueue.offer("Value", 500, TimeUnit.MILLISECONDS);
String value = blockingQueue.poll(500, TimeUnit.MILLISECONDS);
3.阻塞队列的实现原理
3.1 同步器(Synchronizer)的角色
在所有阻塞队列的背后,都运用了同步器的概念,以确保线程安全。同步器是实现锁和其他同步类的有用基础设施。以ReentrantLock为例,让我们概述它如何在队列操作中被利用:
public class MyBlockingQueue<E> {private final ReentrantLock lock = new ReentrantLock();private final LinkedList<E> list = new LinkedList<>();private final Condition notEmpty = lock.newCondition();private final Condition notFull = lock.newCondition();public void put(E e) throws InterruptedException {lock.lock();try {while (list.size() == MAX_CAPACITY) {notFull.await();}list.add(e);notEmpty.signal();} finally {lock.unlock();}}public E take() throws InterruptedException {lock.lock();try {while (list.size() == 0) {notEmpty.await();}E e = list.remove();notFull.signal();return e;} finally {lock.unlock();}}
}
在这个例子中的put和take方法都使用了锁来保证在同一时间只有一个线程可以执行特定代码区域。同时也用到了条件(Conditions)提供了一种能力,让线程声明它在继续前,需要的某个条件为真(例如,“not full"或"not empty”)。
3.2 队列内部结构与数据流转
阻塞队列内部通常使用链表或数组来储存数据。例如,ArrayBlockingQueue 使用一个数组,而LinkedBlockingQueue 用一个链表节点。当一个元素被插入或移除时,队列使用锁防止多个线程的干扰,并利用条件来处理是否需要阻塞线程或唤醒等待的线程。下面是一个基于数组的阻塞队列的简化示例,展示数据结构和同步机制:
public class ArrayBlockingQueue<E> {private final E[] array;private int takeIndex;private int putIndex;private int count;// ... 省略锁和其他成员变量public void put(E e) throws InterruptedException {// ... 锁和条件的使用while (count == array.length) {// 阻塞条件}enqueue(e); // 实际的入队操作// ... 状态修改与线程唤醒}public E take() throws InterruptedException {// ... 锁和条件的使用while (count == 0) {// 阻塞条件// 阻塞条件}E x = dequeue(); // 实际的出队操作// ... 状态修改与线程唤醒return x;}private void enqueue(E e) {array[putIndex] = e;if (++putIndex == array.length) {putIndex = 0;}count++;notEmpty.signal();}private E dequeue() {E x = array[takeIndex];array[takeIndex] = null;if (++takeIndex == array.length) {takeIndex = 0;}count--;notFull.signal();return x;}// ... 其他方法和内部类省略
}
在enqueue方法中,如果putIndex达到数组的末端,则会循环回到数组的起始,形成一个环形结构,以最大化数组的使用。同样地,在dequeue中,索引以同样的方式操作。这种方式优化了队列的储存能力,使其对数组的使用变得连续和高效。
为了防止数组在入队和出队时超出界限,我们使用count来记录队列中元素的数量。当队列为空(count == 0)或满(count == array.length)时,相应的线程将会阻塞等待。
现在,这个简化的模型展示了阻塞队列如何依靠锁来保证并发控制,并使用条件(conditional variables)来同步线程间的协作。
4.Java并发包中的阻塞队列类详解
4.1 ArrayBlockingQueue的结构与特征
ArrayBlockingQueue是一个由数组支撑的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。可选地,可以在构造函数内部定义队列的公平性,如果公平性设定为true,那么等待时间最长的线程会优先得到处理。
ArrayBlockingQueue<Integer> abq = new ArrayBlockingQueue<>(10, true);
4.1.1 公平性对性能的影响
尽管公平锁能够防止线程饥饿,但是它们比非公平锁对性能的影响更大,因为公平锁会降低吞吐量。每次插入或删除操作时,它都需要确保等待队列中的线程按照它们请求访问的顺序获得锁。
4.1.2 ArrayBlockingQueue的适用场景
由于ArrayBlockingQueue的内部是一个固定长度的数组,因此它适用于有明确大小限制的场景,且当您需要平衡生产者和消费者的工作速度时,此阻塞队列非常适合。
4.2 LinkedBlockingQueue的并发优化
LinkedBlockingQueue是一个基于已链接节点的,可选择有界或无界的阻塞队列。在性能调优和并发水平方面,它通常比ArrayBlockingQueue更灵活。
4.2.1 分离锁技术分析
LinkedBlockingQueue内部使用了两个锁——一个用于入队,一个用于出队。这意味着入队和出队操作可以并发进行,大大提升了队列的吞吐量。
LinkedBlockingQueue<Integer> lbq = new LinkedBlockingQueue<>();
4.2.2 LinkedBlockingQueue的实践案例
在并发程序设计中,LinkedBlockingQueue通常用来实现生产者-消费者模式,其中多个线程产生任务,另外多个线程消费这些任务。分离锁增加了并发度,使得在高负载时也能保持高性能。
4.3 PriorityBlockingQueue的优先级排序机制
该队列是一个无界的并发队列,它使用优先级堆来对元素进行排序。元素需要实现Comparable接口,队列利用元素的自然顺序或者根据构造器提供的Comparator确定出队的顺序。
PriorityBlockingQueue<Task> pbq = new PriorityBlockingQueue<>(initialCapacity, comparator);
4.3.1 compareTo方法的实现与应用
为了在PriorityBlockingQueue中使用自定义的排序,元素类需要实现Comparable接口,并重写compareTo方法来定义排序逻辑。
public class Task implements Comparable<Task> {private int priority;// ...public int compareTo(Task o) {return Integer.compare(this.priority, o.getPriority());}
}
4.3.2 应用PriorityBlockingQueue的示例
一个常见的使用场景是任务调度,其中优先级更高的任务应该先被执行。PriorityBlockingQueue确保了最紧急的任务总是先被处理。
4.4 DelayQueue的延迟策略
DelayQueue是一个无界阻塞队列,只有在元素的延迟到期时才能从队列中取出。这一特性使它非常适合于实现缓存失效特性和定时任务调度。
4.4.1 DelayQueue的工作原理
队列中的元素必须实现Delayed接口,并需要定义一个getDelay方法来指定元素到期的时间。
public class DelayedElement implements Delayed {private final long delayTime; // 延迟时间private final long expire; // 到期时间// ... 构造器和其他方法@Overridepublic long getDelay(TimeUnit unit) {long diff = expire - System.currentTimeMillis();return unit.convert(diff, TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {if (this.expire < ((DelayedElement) o).expire) {return -1;}if (this.expire > ((DelayedElement) o).expire) {return 1;}return 0;}
}
DelayQueue使用这些信息来确保只有过期元素才会出队。例如,定时任务的执行,或者使用在缓存中,确保对象只在它们有效的时候才存在于缓存中。
4.4.2 延迟队列的典型应用
在实际应用中,像是缓存系统中自动删除过期条目、任务调度中延迟执行任务等场景都可以使用DelayQueue。
4.5 SynchronousQueue的即时交互特性
SynchronousQueue是一种没有内部容量的队列,每个插入操作都必须等待一个相应的删除操作,反之亦然。因此,SynchronousQueue并不真正存储元素,更多的像是一种线程间交传的机制。
SynchronousQueue<Integer> synQueue = new SynchronousQueue<>();
4.5.1 SynchronousQueue的特别之处
SynchronousQueue的一个关键特性是它不存储元素。如果没有任何线程等待获取元素,那么试图放入元素会阻塞,直至有另一个线程来取走它。
4.5.2 使用SynchronousQueue进行线程间通信
这种队列通常用于直接的线程间通信。比如分布式系统中,你可能使用SynchronousQueue在工作者线程间直接交换任务。
4.6 LinkedTransferQueue的特性与应用
LinkedTransferQueue是一个由链表结构组成的无界阻塞队列。除了常见的阻塞队列操作外,它还提供了transfer和tryTransfer方法,用于即时的元素传递。
LinkedTransferQueue<Integer> ltq = new LinkedTransferQueue<>();
它就像一个LinkedBlockingQueue和SynchronousQueue的混合体,既能存储元素,也能直接交换元素。
4.7 LinkedBlockingDeque双端队列的灵活性
最后,LinkedBlockingDeque是一个可选有界的阻塞双端队列,允许线程从队列的两端插入和移除元素,这为某些特定的使用场景提供了便利。
LinkedBlockingDeque<Task> deque = new LinkedBlockingDeque<>();
它能够从两端进行插入和移除操作,使得它成为一种扩展了功能的队列,可以灵活应对需求的变化。
5.阻塞队列的使用技巧与最佳实践
5.1 阻塞队列在生产者-消费者模型中的应用
一个常见的应用场景是生产者-消费者模型,在这种场景中,生产者创建数据放入队列,消费者从队列中取出数据进行处理。阻塞队列自然地协调了生产者和消费者之间的速度,它确保当队列满时生产者会等待,而队列空时消费者会等待。
常见的做法是生产者和消费者分别在不同的线程或者线程池中执行,以此来提高整个系统的并行度:
ExecutorService producerPool = Executors.newFixedThreadPool(N_PRODUCERS);
ExecutorService consumerPool = Executors.newFixedThreadPool(N_CONSUMERS);
for (int i = 0; i < N_PRODUCERS; i++) {producerPool.submit(() -> {while (true) {// 生产数据queue.put(produceData());}});
}
for (int i = 0; i < N_CONSUMERS; i++) {consumerPool.submit(() -> {while (true) {// 消费数据consumeData(queue.take());}});
}
5.2 多线程环境下阻塞队列的使用注意事项
在使用阻塞队列时,要注意线程的中断策略。在等待插入或移除操作的阻塞过程中,线程可能会被中断,正确的中断处理策略可以避免资源泄露或者不一致的状态。以下是处理中断的一种推荐方法:
try {queue.put(data);
} catch (InterruptedException e) {// 线程被中断的处理Thread.currentThread().interrupt(); // 重新设置中断状态return; // 或根据情况进行其他处理,例如重试或者退出
}
5.3性能调优与监控
性能优化是使用阻塞队列时的关键考虑点。你可以通过调整线程池大小、队列容量和使用正确类型的阻塞队列来优化性能。例如,如果你的应用场景涉及多生产者和多消费者,你可能会考虑使用LinkedBlockingQueue而不是ArrayBlockingQueue,因为前者在多线程环境下具有更好的吞吐量。
同时,监控队列的状态也非常重要,它可以帮助你理解系统性能及时发现潜在的问题,例如,队列的大小、增长趋势、丢弃的任务数等。
6.码分析阻塞队列的具体实现
结合源码来分析是理解Java阻塞队列内在机制的绝佳方式。通过具体的代码示例,我们可以更深入地理解前面提到的概念和细节。
6.1 ArrayBlockingQueue源码剖析
ArrayBlockingQueue在Java的并发包中是一种经典的有界队列实现。以下是它源码的简化版本,侧重于其核心功能:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {/ The queued items */private final E[] items;/ items index for next take, poll, peek or remove /private int takeIndex;/** items index for next put, offer, or add/private int putIndex;/ Number of elements in the queue */private int count;/ Main lock guarding all access /final ReentrantLock lock;/** Condition for waiting takes/private final Condition notEmpty;/** Condition for waiting puts */private final Condition notFull;// ... 构造函数和其他方法省略public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length)notFull.await();enqueue(e);} finally {lock.unlock();}}public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}}private void enqueue(E e) {// circularly increment indexitems[putIndex] = e;if (++putIndex == items.length)putIndex = 0;count++;notEmpty.signal();}private E dequeue() {// similar circularly decrement on takeIndexE x = items[takeIndex];items[takeIndex] = null; // for GCif (++takeIndex == items.length)takeIndex = 0;count--;notFull.signal();return x;}// ...其他方法和内部类
}
在ArrayBlockingQueue中,enqueue方法在队列尾部添加元素,dequeue方法从头部移除元素。通过循环索引的方式优化了数组的使用,使得队列的前端和后端可以在数组的任意位置。当putIndex和takeIndex相遇时,这种设计允许队列无缝地从数组末尾回绕到开始位置。
使用两个条件变量notEmpty和notFull分别对空和满的情况进行线程阻塞和唤醒,这允许线程在条件不满足时等待(比如空队列或满队列),并且在条件改变时得到通知,从而恢复执行。
6.2 LinkedBlockingQueue源码解读
LinkedBlockingQueue则使用链表节点结构存储元素,初始容量几乎无限制,但可选择定义其界限。它的实现利用了两把锁——一把用于控制入队操作,一把用于出队操作,从而实现了更好的并发性。
public class LinkedBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {// ...节点定义和其他成员变量private final int capacity;private final AtomicInteger count = new AtomicInteger();transient Node<E> head;private transient Node<E> last;private final ReentrantLock takeLock = new ReentrantLock();private final Condition notEmpty = takeLock.newCondition();private final ReentrantLock putLock = new ReentrantLock();private final Condition notFull = putLock.newCondition();// ...构造函数和其他方法public void put(E e) throws InterruptedException {if (e == null) throw new NullPointerException();// Note: Convention in all put/take/etc is to preset local var// holding count negative to indicate failure unless set.int c = -1;Node<E> node = new Node<E>(e);final ReentrantLock putLock = this.putLock;putLock.lockInterruptibly();try {while (count.get() == capacity) {notFull.await();}enqueue(node);c = count.getAndIncrement();if (c + 1 < capacity)notFull.signal();} finally {putLock.unlock();}if (c == 0)signalNotEmpty();}private void signalNotEmpty() {final ReentrantLock takeLock = this.takeLock;takeLock.lock();try {notEmpty.signal();} finally {takeLock.unlock();}}private void enqueue(Node<E> node) {// Always put at lastlast = last.next = node;}public E take() throws InterruptedException {E x;int c = -1;final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;takeLock.lockInterruptibly();try {while (count.get() == 0) {notEmpty.await();}x = dequeue();c = count.getAndDecrement();if (c > 1)notEmpty.signal();} finally {takeLock.unlock();}if (c == capacity)signalNotFull();return x;}private E dequeue() {// Always take from headNode<E> h = head;Node<E> first = h.next;h.next = h; // Help GChead = first;E x = first.item;first.item = null;return x;}// ...其他方法和内部类
}
在LinkedBlockingQueue中,enqueue方法将新节点添加到尾节点的下一个位置,并更新last指针。dequeue方法则从头结点的下一个节点取出元素,因为头节点是一个空的哑元节点,用于简化边界检查和获取锁的过程。
两个锁putLock和takeLock保证了入队和出队操作的线程安全性,且延续了先前在ArrayBlockingQueue讨论中的条件变量模型,使用它们分别处理非满和非空的阻塞情况。
6.3 基于源码深度优化阻塞队列性能
了解了阻塞队列如ArrayBlockingQueue和LinkedBlockingQueue的源码实现后,我们可以考虑如何根据应用场景对它们进行性能优化。优化可以涉及多个方面:
- 在有明确容量限制的环境中更青睐ArrayBlockingQueue。
- 在需要高吞吐量的环境中使用LinkedBlockingQueue,特别是双向锁带来的并发优势。
- 调整条件变量的使用,或者完全替换同步机制,比如使用java.util.concurrent.locks包中的其他锁实现。
- 监测和分析锁竞争情况,以及等待时间,为调优提供数据基础。
相关文章:
高效并发编程:Java阻塞队列深度解析与最佳实践
1.阻塞队列的基本概念与应用场景 1.1 阻塞队列的定义 阻塞队列(BlockingQueue)是Java并发包中的一个接口,它支持两个附加操作:当队列为空时,获取元素的线程会等待队列变为非空;当队列满时,存储…...
会话机制:Session
1、什么是会话: 会话对应的英语单词:session 用户打开浏览器,进行一系列操作,然后最终将浏览器关闭,这个整个过程叫做:一次会话。会话在服务器端也有一个对应的java对象,这个java对象叫做&…...
MySQL中, 自增主键和UUID作为主键有什么区别?
首先我们来看看, 存储自增主键和uuid的数据类型 我们知道, mysql中作为主键的通常是int类型的数据, 这个 数据从第一条记录开始, 从1开始主键往后递增, 例如我有100条数据, 那么根据主键排序后, 里面的记录从上往下一次就是1, 2, 3 ... 100, 但是UUID就不一样了, UUID是根据特殊…...
机器人与AI:结合应用与未来展望
机器人与AI:结合应用与未来展望 引言 机器人与人工智能(AI)的结合已经成为现代科技发展的重要方向。随着AI技术的不断进步,机器人不仅在工业领域中得到广泛应用,还逐渐渗透到家庭、医疗、服务等各个领域。本文将探讨…...
PyTorch学习笔记:新冠肺炎X光分类
前言 目的是要了解pytorch如何完成模型训练 https://github.com/TingsongYu/PyTorch-Tutorial-2nd参考的学习笔记 数据准备 由于本案例目的是pytorch流程学习,为了简化学习过程,数据仅选择了4张图片,分为2类,正常与新冠…...
【Python】 XGBoost模型的使用案例及原理解析
原谅把你带走的雨天 在渐渐模糊的窗前 每个人最后都要说再见 原谅被你带走的永远 微笑着容易过一天 也许是我已经 老了一点 那些日子你会不会舍不得 思念就像关不紧的门 空气里有幸福的灰尘 否则为何闭上眼睛的时候 又全都想起了 谁都别说 让我一个人躲一躲 你的承诺 我竟然没怀…...
Java中print,println,printf的功能以及区别
在Java中,System.out.print, System.out.println, 和 System.out.printf 都是用于在控制台输出的方法,但它们在使用和功能上有所不同。 System.out.print: * 功能:将指定的内容输出到控制台,但不换行。 * 示例:Sy…...
vue3+electron+typescript 项目安装、打包、多平台踩坑记录
环境说明 这里的测试如果没有其他特别说明的,就是在win10/i7环境,64位 创建项目 vite官方是直接支持创建electron项目的,所以,这里就简单很多了。我们已经不需要向开始那样自己去慢慢搭建 yarn create vite这里使用yarn创建&a…...
实际案例分析
实际案例分析 一、数据准备与特征工程 1.1数据收集 在实际案例分析中,首先需要收集相关数据。数据来源可以包括公开数据集、企业内部数据、互联网爬虫抓取等。为了保证数据的质量和准确性,数据收集过程中需遵循以下原则: -针对性强&#…...
JAVA实现图书管理系统(初阶)
一.抽象出对象: 1.要有书架,图书,用户(包括普通用户,管理员用户)。根据这些我们可以建立几个包,来把繁杂的代码分开,再通过一个类来把这些,对象整合起来实现系统。说到整合…...
【Torch学习笔记】
作者:zjk 和 的区别是逐元素相乘,是矩阵相乘 cat stack 的区别 cat stack 是用于沿新维度将多个张量堆叠在一起的函数。它要求所有输入张量具有相同的形状,并在指定的新维度上进行堆叠。...
LeetCode算法题:42. 接雨水(Java)
题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3…...
LINGO:存贮问题
存贮模型中的基本概念 模型: 基本要素: (1)需求率:单位时间内对某种物品的需求量,用D表示。 (2)订货批量:一次订货中,包含某种货物的数量,用 Q表…...
《微服务王国的守护者:Spring Cloud Dubbo的奇幻冒险》
5. 经典问题与解决方案 5.3 服务追踪与链路监控 在微服务架构的广袤宇宙中,服务间的调用关系错综复杂,如同一张庞大的星系网络。当一个请求穿越这个星系,经过多个服务节点时,如何追踪它的路径,如何监控整个链路的健康…...
(九)npm 使用
视频链接:尚硅谷2024最新版微信小程序 文章目录 使用 npm 包自定义构建 npmVant Weapp 组件库的使用Vant Weapp 组件样式覆盖使用 npm 包 目前小程序已经支持使用 npm 安装第三方包,因为 node_modules 目录中的包不会参与小程序项目的编译、上传和打包, 因此在小程序项目中要…...
Thinkphp5内核宠物领养平台H5源码
源码介绍 Thinkphp5内核流浪猫流浪狗宠物领养平台H5源码 可封装APP,适合做猫狗宠物类的发信息发布,当然懂的修改一下,做其他信息发布也是可以的。 源码预览 源码下载 https://download.csdn.net/download/huayula/89361685...
一、Elasticsearch介绍与部署
目录 一、什么是Elasticsearch 二、安装Elasticsearch 三、配置es 四、启动es 1、下载安装elasticsearch的插件head 2、在浏览器,加载扩展程序 3、运行扩展程序 4、输入es地址就可以了 五、Elasticsearch 创建、查看、删除索引、创建、查看、修改、删除文档…...
NL6621 实现获取天气情况
一、主要完成的工作 1、建立TASK INT32 main(VOID) {/* system Init */SystemInit();OSTaskCreate(TestAppMain, NULL, &sAppStartTaskStack[NST_APP_START_TASK_STK_SIZE -1], NST_APP_TASK_START_PRIO); OSStart();return 1; } 2、application test task VOID TestAp…...
SpringCloud配置文件bootrap
解决方案: 情况一、SpringBoot 版本 小于 2.4.0 版本,添加以下依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-context</artifactId> </dependency> 情况二、SpringBoot…...
经典面试题:进程、线程、协程开销问题,为什么进程切换的开销比线程的大?
上下文切换的过程? 上下文切换是操作系统在将CPU从一个进程切换到另一个进程时所执行的过程。它涉及保存当前执行进程的状态并加载下一个将要执行的进程的状态。下面是上下文切换的详细过程: 保存当前进程的上下文: 当操作系统决定切换到另…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
