【JavaEE】阻塞队列

目录

一.阻塞队列(Blocking Queue)

1.什么是阻塞队列

2.特性

二.生产者消费者模型

1.什么是生产者消费者模型?

2.生产者消费模型的好处

2.1解耦合  

2.2削峰填谷

三.如何在java中使用阻塞队列

 四.模拟实现阻塞队列

1.加锁

2.阻塞等待实现

3.解决interrupt唤醒waitting问题

4.处理因指令重排序而导致的线程安全问题

测试


 在前一篇中,我们讲到了单例模式中的饿汉模式和懒汉模式,本篇我们来讲在多线程常用的阻塞队列。

一.阻塞队列(Blocking Queue)

1.什么是阻塞队列

阻塞队列是一种特殊的队列,也有着“先进先出”的性质。

2.特性

阻塞队列是一种线程安全的数据结构,具有以下的特性:

  • 当队列满时,继续入队列就会进行阻塞等待,直到其他线程从队列中取出元素
  • 当队列为空时,继续出队列也会进行阻塞对待,直到队列中的元素不为空

阻塞在实际开发应用中,一个典型的应用场景就是“生产者消费者模型”。

二.生产者消费者模型

1.什么是生产者消费者模型?

生产者消费者模型就是通过一个容器来解决生产者和消费者之间的强耦合问题。生产者和消费者之间不直接联系,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,而是直接给队列,消费者就不直接找生产者,而是直接从阻塞队列里取。

2.生产者消费模型的好处

  1. 解耦合:生产者和消费者之间不再直接联系,而是通过阻塞队列。
  2. 削峰填谷:阻塞队列就相当于一个缓冲区,平衡了生产者和消费者之间的处理能力。

2.1解耦合  

我们在开发中, 有些时候可能需要两个或者多个模块或者组件之间的依赖关系不要太紧密,那我们就需要进行解耦合。

解耦合的好处

  1. 易于维护:当一个模块发生改变时,不会影响到其他模块,减少了修改一处bug而导致多重地方出现bug的风险。
  2. 可扩展性:可以轻松地将新的功能或者模块添加到系统中,无需对系统重构。
  3. 可重用性:独立的模块可以中不同的场景或者项目中使用,提高了代码的复用率。
  4. 简化测试:独立的模块更容易进行单元测试,因为它们不需要依赖复杂的外部环境或状态。

示例:假设现有服务器A和服务器B是直接相联的

服务器A和服务器B是交互是直接的,之间的耦合度是非常高的,若服务器A或者B崩溃,会直接导致服务器B或A也崩溃。

对于上述这种情况,我们就可以使用生产者消费者模型来解决,通过一个阻塞队列,来间接地传递和接受信息。

当服务器A想要向服务器B传递信息时,就将信息传递到阻塞队列中,服务器B再从阻塞队列中将信息取出。通过这个方式,服务器之间的耦合度就大大降低,服务器A若崩溃,服务器B也不会崩溃。

若我们想要添加一个服务器C,那么也不需要对其他服务器做任何修改,直接向阻塞队列请求即可。

2.2削峰填谷

 在高并发的情况下,系统短时间内可能会面临大量数据请求,导致系统资源耗尽。可以使用阻塞队列来削峰填谷,将请求存储在阻塞队列中,按照系统处理能力逐渐消费信息,从而达到削峰填谷的效果,避免系统崩溃或性能降低。

示例:依旧是以上图为例,假设现有大量请求涌进服务器A中,那么此时服务器A不会直接将请求传递给服务器B,若直接传输,会导致服务器B处理不过来直接崩溃。服务器A会将请求传递到阻塞队列中,当阻塞队列满之后,此时阻塞队列不再会接收服务器A的入队请求,而是要等阻塞队列中不为满时才接收服务器A的请求;服务器B从阻塞队列中接收请求并进行处理,直到队列中的请求被服务器B处理完。

三.如何在java中使用阻塞队列

 在java中给我们提供BlockingQueue接口

由于BlockingQueue使一个接口,不能直接去实例化,java中有以下类实现了该接口。

同时我们可以看到,BlockingQueue继承了Queue,所以Queue中offer和poll在BlockingQueue中也有,但我们不使用这两个,需要使用put和take方法才具有阻塞。

 这里我们使用第一个类来做演示:

实现一对一的生产者消费者模型。

public class Demo12 {/*** 程序的入口点。* 创建一个ArrayBlockingQueue,并启动两个线程。一个线程负责生产数字字符串,另一个线程负责消费这些字符串。* 这个示例展示了生产者-消费者模式的使用,其中ArrayBlockingQueue作为共享资源,用于在生产者和消费者之间同步数据。** @param args 命令行参数* @throws InterruptedException 如果线程在执行过程中被中断*/public static void main(String[] args) throws InterruptedException {// 创建一个容量为1000的ArrayBlockingQueue,用于生产者和消费者之间的通信BlockingQueue<String> BQ = new ArrayBlockingQueue<>(1000);// 创建生产者线程,负责向队列中添加数字字符串Thread t1 = new Thread(() -> {int i = 0;while (true) {try {// 将数字字符串添加到队列中,如果队列满,则阻塞直到有空间可用BQ.put("" + i);i++;} catch (InterruptedException e) {// 如果线程被中断,抛出运行时异常throw new RuntimeException(e);}System.out.println("生产了" + i);}});// 创建消费者线程,负责从队列中取出数字字符串并消费Thread t2 = new Thread(() -> {while (true) {try {// 从队列中取出一个数字字符串,如果队列为空,则阻塞直到有元素可用String s = BQ.take();System.out.println("消费了" + s);// 模拟消费过程,让线程休眠1秒Thread.sleep(1000);} catch (InterruptedException e) {// 如果线程被中断,抛出运行时异常throw new RuntimeException(e);}}});// 启动生产者和消费者线程t1.start();t2.start();}}

 四.模拟实现阻塞队列

阻塞队列的容量是有一定大小的,所以我们首先需要实现一个循环队列。

判空判满条件:

当size==0时,说明队列为空

当size==q.length时,说明队列满了。

如何解决当head或者ptail走到队列末尾,但size不等于队列长度的情况?

当head走到末尾之后,若head等于q.length时,让head=0即可。

同理,当ptail等于q.length时,让ptail=0即可。

/*** MyBlockingQueues 类实现了阻塞队列的数据结构。* 阻塞队列是一种线程安全的队列,当队列为空时,take 方法会阻塞,直到队列有元素可用;* 当队列满时,put 方法会阻塞,直到队列有空闲位置。*/
class MyBlockingQueues {// 存储队列元素的数组private String[] q;// 指向队列头部的索引private int head;// 指向队列尾部的索引private int ptail;// 当前队列中元素的数量private int size;/*** 构造函数初始化阻塞队列。* * @param capacity 队列的容量,即队列最多可以存储的元素数量。*/public MyBlockingQueues(int capacity) {q = new String[capacity];size = 0;}/*** 将元素添加到队列中。* 如果队列已满,则此方法会阻塞,直到队列有空闲位置。* * @param s 要添加到队列的元素。*/public void put(String s){// 判断队列是否已满,如果已满则返回,否则继续添加元素//判断队列是否满了if(size==q.length){return;}q[ptail++]=s;// 如果尾部索引超过数组长度,则重置为0,实现循环队列if(ptail>=q.length){ptail=0;}size++;}/*** 从队列中取出一个元素。* 如果队列为空,则此方法会阻塞,直到队列有元素可用。* * @return 取出的元素。*/public String take(){String ret="";// 判断队列是否为空,如果为空则返回空字符串,否则继续取出元素if(size==0){return ret;}ret=q[head++];// 如果头部索引超过数组长度,则重置为0,实现循环队列if(head>=q.length){head=0;}size--;return ret;}}

1.加锁

当我们实现玩一个循环之后,接下来要实现的就是阻塞队列,阻塞队列,顾名思义就是在队列空/满的时候进行等待,但阻塞队列通常是在多线程下使用的,所以我们需要考虑线程安全问题。

在入队列和出队列的时候,代码块中的代码基本都涉及到了修改操作,因此,我们可以直接对整个代码块进行加锁。

class MyBlockingQueues {// 存储队列元素的数组private String[] q;// 指向队列头部的索引private int head;// 指向队列尾部的索引private int ptail;// 当前队列中元素的数量private int size;/*** 构造函数初始化阻塞队列。** @param capacity 队列的容量,即队列最多可以存储的元素数量。*/public MyBlockingQueues(int capacity) {q = new String[capacity];size = 0;}/*** 将元素添加到队列中。* 如果队列已满,则此方法会阻塞,直到队列有空闲位置。** @param s 要添加到队列的元素。*/public void put(String s){synchronized(this) {// 判断队列是否已满,如果已满则返回,否则继续添加元素//判断队列是否满了if (size == q.length) {return;}q[ptail++] = s;// 如果尾部索引超过数组长度,则重置为0,实现循环队列if (ptail >= q.length) {ptail = 0;}size++;}}/*** 从队列中取出一个元素。* 如果队列为空,则此方法会阻塞,直到队列有元素可用。** @return 取出的元素。*/public String take(){String ret="";synchronized (this) {// 判断队列是否为空,如果为空则返回空字符串,否则继续取出元素if (size == 0) {return ret;}ret = q[head++];// 如果头部索引超过数组长度,则重置为0,实现循环队列if (head >= q.length) {head = 0;}size--;}return ret;}}

2.阻塞等待实现

当线程安全问题解决之后,我们现在要考虑的就是如何让队列能够进行阻塞等待,我们需要用到wait和notify方法来实现。

在put方法中,当size==q.length时,说明此时队列中已满,需要进行阻塞等待。若没有满则进行入队操作,当入完队之后就可以通知take进行出队操作。

在take方法中,当size==0时,说明此时队列为空,此时同样需要进行阻塞等待(等put通知),若队列不为空,则进行出队操作,当出完队之后就可以通知put进行入队操作。

/*** MyBlockingQueues 类实现了阻塞队列的数据结构。* 阻塞队列是一种线程安全的队列,当队列为空时,take 方法会阻塞,直到队列有元素可用;* 当队列满时,put 方法会阻塞,直到队列有空闲位置。*/
class MyBlockingQueues {// 存储队列元素的数组private String[] q;// 指向队列头部的索引private int head;// 指向队列尾部的索引private int ptail;// 当前队列中元素的数量private int size;/*** 构造函数初始化阻塞队列。** @param capacity 队列的容量,即队列最多可以存储的元素数量。*/public MyBlockingQueues(int capacity) {q = new String[capacity];size = 0;}/*** 将元素添加到队列中。* 如果队列已满,则此方法会阻塞,直到队列有空闲位置。** @param s 要添加到队列的元素。*/public void put(String s) throws InterruptedException {synchronized(this) {// 判断队列是否已满,如果已满则返回,否则继续添加元素//判断队列是否满了if (size == q.length) {this.wait();}q[ptail++] = s;// 如果尾部索引超过数组长度,则重置为0,实现循环队列if (ptail >= q.length) {ptail = 0;}size++;}}/*** 从队列中取出一个元素。* 如果队列为空,则此方法会阻塞,直到队列有元素可用。** @return 取出的元素。*/public String take() throws InterruptedException {String ret="";synchronized (this) {// 判断队列是否为空,如果为空则返回空字符串,否则继续取出元素if (size == 0) {this.wait();}ret = q[head++];// 如果头部索引超过数组长度,则重置为0,实现循环队列if (head >= q.length) {head = 0;}size--;}return ret;}}

3.解决interrupt唤醒waitting问题

唤醒wait的不仅有notify,而且还有interrupt,能够唤醒阻塞之后中断线程并且抛出InterruptedException。

在put方法中,如果size==q.length,此时调用put的线程进入阻塞等待(waitting)状态,如果此时使用interrupt会唤醒阻塞并将线程终止。

在take方法中,如果size==0,此时调用take的线程就也会进入阻塞等待(waitting)状态,如果此时使用interrupt会唤醒阻塞并将线程终止。

若对于我们上面的代码中,调用interrupt,线程确实会终止。

我们可以查看一下wait方法的文档说明。

我们可以看到java中建议我们使用while循环来检查等待条件。

为什么可以使用if,还建议使用while?

假设在判断队列是否为空/满的时候。调用wait的方法的时候使用try-catch语句来抛异常,但在catch中忘记添加抛出异常的语句,再调用interrupt会发生什么?

同理,在take方法中,当size=0时,此时wait正在等待,若被唤醒,就会进行出队操作,但由于此时队列中没有数据,出队列操作没有意义。

为了防止这些bug出现,所以我们使用while更加稳妥,当wait被interrupt唤醒之后,会再次判断当前size是否满足判断条件,若满足则继续执行wait,而不会直接执行后序的代码。反之,若size不为0或者q.length,则会跳出循环,执行后序代码。

    public void put(String s)  {synchronized(this) {// 判断队列是否已满,如果已满则返回,否则继续添加元素//判断队列是否满了while (size == q.length) {try {this.wait();} catch (InterruptedException e) {}}q[ptail++] = s;// 如果尾部索引超过数组长度,则重置为0,实现循环队列if (ptail >= q.length) {ptail = 0;}size++;}}

修改后的完整代码:

/*** 自定义阻塞队列类,用于实现具有固定容量的线程安全队列。* 队列满时,生产者线程会被阻塞,直到队列有空位;队列空时,消费者线程会被阻塞,直到队列有元素。*/
class MyBlockingQueues {// 存储队列元素的数组private String[] q;// 指向队列头部的索引private int head;// 指向队列尾部的索引private int ptail;// 当前队列中元素的数量private int size;/*** 构造函数初始化阻塞队列。** @param capacity 队列的容量,即队列最多可以存储的元素数量。*/public MyBlockingQueues(int capacity) {q = new String[capacity];size = 0;}/*** 将一个字符串元素添加到队列中。* 如果队列已满,则当前线程被阻塞,直到队列有空位。** @param s 要添加到队列的字符串元素。* @throws InterruptedException 如果线程在等待时被中断。*/public void put(String s) throws InterruptedException {synchronized(this) {// 当队列满时,等待直到有空位// 判断队列是否已满,如果已满则返回,否则继续添加元素//判断队列是否满了while (size == q.length) {this.wait();}q[ptail++] = s;// 当尾部索引达到数组长度时,重置为0// 如果尾部索引超过数组长度,则重置为0,实现循环队列if (ptail >= q.length) {ptail = 0;}size++;// 唤醒等待的消费者线程this.notifyAll();}}/*** 从队列中取出一个字符串元素。* 如果队列为空,则当前线程被阻塞,直到队列有元素。** @return 从队列中取出的字符串元素。* @throws InterruptedException 如果线程在等待时被中断。*/public String take() throws InterruptedException {String ret="";synchronized (this) {// 当队列空时,等待直到有元素// 判断队列是否为空,如果为空则返回空字符串,否则继续取出元素while (size == 0) {this.wait();}ret = q[head++];// 当头部索引达到数组长度时,重置为0// 如果头部索引超过数组长度,则重置为0,实现循环队列if (head >= q.length) {head = 0;}size--;// 唤醒等待的生产者线程this.notifyAll();}return ret;}}

4.处理因指令重排序而导致的线程安全问题

在编译器和处理器层面,为了提高性能,可能会对指令进行重排序。由于阻塞队列常用于多线程中,且在put和take要对字段进行频繁的读写操作,为了防止因指令重排序而导致的问题,这里我们需要使用volatie来修饰成员变量,来防止编译器的优化。

/*** 自定义阻塞队列类,用于实现具有固定容量的线程安全队列。* 队列满时,生产者线程会被阻塞,直到队列有空位;队列空时,消费者线程会被阻塞,直到队列有元素。*/
class MyBlockingQueues {// 存储队列元素的数组private volatile String[] q;// 指向队列头部的索引private volatile int head;// 指向队列尾部的索引private volatile int ptail;// 当前队列中元素的数量private volatile int size;/*** 构造函数初始化阻塞队列。** @param capacity 队列的容量,即队列最多可以存储的元素数量。*/public MyBlockingQueues(int capacity) {q = new String[capacity];size = 0;}/*** 将一个字符串元素添加到队列中。* 如果队列已满,则当前线程被阻塞,直到队列有空位。** @param s 要添加到队列的字符串元素。* @throws InterruptedException 如果线程在等待时被中断。*/public void put(String s) throws InterruptedException {synchronized(this) {// 当队列满时,等待直到有空位// 判断队列是否已满,如果已满则返回,否则继续添加元素//判断队列是否满了while (size == q.length) {this.wait();}q[ptail++] = s;// 当尾部索引达到数组长度时,重置为0// 如果尾部索引超过数组长度,则重置为0,实现循环队列if (ptail >= q.length) {ptail = 0;}size++;// 唤醒等待的消费者线程this.notifyAll();}}/*** 从队列中取出一个字符串元素。* 如果队列为空,则当前线程被阻塞,直到队列有元素。** @return 从队列中取出的字符串元素。* @throws InterruptedException 如果线程在等待时被中断。*/public String take() throws InterruptedException {String ret="";synchronized (this) {// 当队列空时,等待直到有元素// 判断队列是否为空,如果为空则返回空字符串,否则继续取出元素while (size == 0) {this.wait();}ret = q[head++];// 当头部索引达到数组长度时,重置为0// 如果头部索引超过数组长度,则重置为0,实现循环队列if (head >= q.length) {head = 0;}size--;// 唤醒等待的生产者线程this.notifyAll();}return ret;}}

测试

这里我们设置阻塞队列长度为100.

先让生产者生成100个产品再进行消费。

/*** 自定义阻塞队列类,用于实现具有固定容量的线程安全队列。* 队列满时,生产者线程会被阻塞,直到队列有空位;队列空时,消费者线程会被阻塞,直到队列有元素。*/
class MyBlockingQueues {// 存储队列元素的数组private volatile String[] q;// 指向队列头部的索引private volatile int head;// 指向队列尾部的索引private volatile int ptail;// 当前队列中元素的数量private volatile int size;/*** 构造函数初始化阻塞队列。** @param capacity 队列的容量,即队列最多可以存储的元素数量。*/public MyBlockingQueues(int capacity) {q = new String[capacity];size = 0;}/*** 将一个字符串元素添加到队列中。* 如果队列已满,则当前线程被阻塞,直到队列有空位。** @param s 要添加到队列的字符串元素。* @throws InterruptedException 如果线程在等待时被中断。*/public void put(String s) throws InterruptedException {synchronized(this) {// 当队列满时,等待直到有空位// 判断队列是否已满,如果已满则返回,否则继续添加元素//判断队列是否满了while (size == q.length) {this.wait();}q[ptail++] = s;// 当尾部索引达到数组长度时,重置为0// 如果尾部索引超过数组长度,则重置为0,实现循环队列if (ptail >= q.length) {ptail = 0;}size++;// 唤醒等待的消费者线程this.notifyAll();}}/*** 从队列中取出一个字符串元素。* 如果队列为空,则当前线程被阻塞,直到队列有元素。** @return 从队列中取出的字符串元素。* @throws InterruptedException 如果线程在等待时被中断。*/public String take() throws InterruptedException {String ret="";synchronized (this) {// 当队列空时,等待直到有元素// 判断队列是否为空,如果为空则返回空字符串,否则继续取出元素while (size == 0) {this.wait();}ret = q[head++];// 当头部索引达到数组长度时,重置为0// 如果头部索引超过数组长度,则重置为0,实现循环队列if (head >= q.length) {head = 0;}size--;// 唤醒等待的生产者线程this.notifyAll();}return ret;}
}
class Demos{/*** 程序的入口点。* 创建一个容量为100的MyBlockingQueues实例,用于生产者和消费者之间的通信。* 分别启动两个线程,一个负责生产物品,另一个负责消费物品。* @param args 命令行参数*/public static void main(String[] args) {// 创建一个容量为100的阻塞队列MyBlockingQueues BQ=new MyBlockingQueues(100);// 创建生产者线程,负责向队列中添加物品Thread t1=new Thread(()->{int i=0;while(true){try {// 将物品添加到队列中,并打印生产信息BQ.put(""+i);System.out.println("生产者生产了"+i);i++;} catch (InterruptedException e) {// 打印异常信息e.printStackTrace();}}});// 创建消费者线程,负责从队列中取出物品Thread t2=new Thread(()->{while(true){try {// 模拟消费者等待一段时间Thread.sleep(1000);// 从队列中取出物品,并打印消费信息System.out.println("消费者消费了"+BQ.take());} catch (InterruptedException e) {// 打印异常信息e.printStackTrace();}}});// 启动生产者和消费者线程t1.start();t2.start();}}


阻塞队列就先到这里了~

若有不足,欢迎指正~ 

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

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

相关文章

学习c语言第十三天(结构体)

一.结构体声明 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 描述复杂对象。 结构体成员可以是标量、数组、指针、结构体。 定义和初始化&#xff1a; struct peo {char namer[20];char tele[12];char sex[5];int high; }; s…

git 迁移仓库的方法

git Git是一个开源的分布式版本控制系统&#xff0c;由Linus Torvalds在2005年创建&#xff0c;用于有效、高速地处理从小到大的项目管理。它最初是为Linux内核开发而设计的&#xff0c;但很快被广泛用于各种项目。 以下是Git的一些主要特性&#xff1a; 分布式架构&#xff…

Python设计模式 - 工厂方法模式

定义 工厂方法模式是一种创建型设计模式&#xff0c;它定义一个创建对象的接口&#xff0c;让其子类来处理对象的创建&#xff0c;而不是直接实例化对象。 结构 抽象工厂&#xff08;Factory&#xff09;&#xff1a;声明工厂方法&#xff0c;返回一个产品对象。具体工厂类都…

4.JAVA-运算符

算数运算符 隐式类型转换 强制转换 字符串操作 字符相加 小结 自增自减运算符 赋值运算符 关系运算符 逻辑运算符 短路逻辑运算 三元运算符 运算符优先级 这里小括号优先于所有&#xff0c;所以想要哪一个优先运算&#xff0c;就可以将哪一个用小括号扩起来&#xff0c;比较方便…

酒店押金原路退回系统开通方法,手机查看报表

一、酒店押金管理有哪些&#xff1f; 1.渠道有银行预授权 2.微信押金支付 3.酒店押金系统 4.支付押金管理 二、专业酒店押金管理VS银行 序号功能专业押金系统银行预授权1收款方式支持微信、支付宝、银联app、信用卡、花呗需要带银行卡刷卡2资金安全区分房费和押金&#x…

PermissionError: [Errno 13] Permission denied

PermissionError: [Errno 13] Permission denied 目录 PermissionError: [Errno 13] Permission denied 【常见模块错误】 【错误原因】 【解决方案】 检查文件或目录的权限 确保文件路径正确 关闭其他占用文件的程序 运行程序时提升权限 更改 Python 的工作目录 示例代…

什么是职场?如何在职场中提升自己的情商?

职场这一概念&#xff0c;实质上是指在工作场所中&#xff0c;员工与员工之间、员工与组织之间相互发生作用和影响的一个特定环境。它不仅仅局限于办公室&#xff0c;还延展到会议室、休息室、餐厅等场所&#xff0c;这些场所交织成了一个错综复杂的职场生态系统。在这个系统中…

哪里可以查找短视频素材?6个素材查找下载渠道分享!

在短视频的风靡浪潮中&#xff0c;不少创作者纷纷投身于这一领域&#xff0c;无论是分享生活点滴还是进行商业宣传&#xff0c;高质量的短视频内容总能吸引众多观众的目光。然而&#xff0c;精良的短视频制作离不开优质的素材支持。本文将为大家介绍6个优秀的高质量短视频素材下…

POJ2739.Sum of Consecutive Prime Numbers

欧拉筛处理2-1e4的质数&#xff0c;再用尺取法即可 // Problem: Sum of Consecutive Prime Numbers // Contest: POJ - Japan 2005 // URL: http://poj.org/problem?id2739 // Memory Limit: 65 MB // Time Limit: 1000 ms // // Powered by CP Editor (https://cpeditor.or…

[240727] Qt Creator 14 发布 | AMD 推迟 Ryzen 9000芯片发布

目录 Qt Creator 14 发布Qt Creator 14 版本发布&#xff0c;带来一系列新功能和改进终端用户可通过命令行方式查看此新闻终端用户可通过命令行方式安装软件&#xff1a; AMD 推迟 Ryzen 9000芯片发布 Qt Creator 14 发布 Qt Creator 14 版本发布&#xff0c;带来一系列新功能…

高速板开源项目学习(二)

一定要找一个高速板写的详细的等长规范&#xff1a; 看的出来&#xff0c;这位小哥也是卡着嘉立创最小免费钻孔大小来打孔的&#xff1a; 这里的天线&#xff0c;他做了禁止铺铜和走线处理&#xff0c;模拟信号在这里容易遇到干扰&#xff0c;这样是正确的&#xff0c;值得去学…

《知识点扫盲 · 线程池基础篇》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

【node】Linux下安装node和npm

Linux下安装node和npm 下面的版本虽然安装失败了&#xff0c;第一次尝试不容易&#xff0c;只需要更换一下node的版本为v16.20.2即可安装成功&#xff0c;20这样的高版本对大部分linux服务器来讲还是版本太高了&#xff0c;GLIBC动态库不支持&#xff0c;升级颇为麻烦&#xff…

她是军统美女特工,色诱汉奸一把好手!一件事之后竟......

一.前言 我们在上一篇里简单了解了什么是树&#xff0c;以及树的一种特殊结构——二叉树。而我们对二叉树息息相关的堆进行了简单的介绍。我们知道了堆是借助二叉树中完全二叉树来实现的。它实现了二叉树的顺序存储。但对于普通的二叉树来说&#xff0c;顺序存储会造成空间浪费…

贪心+背包

这道题比较坑的就是我们的对于相同截止时间的需要排个序&#xff0c;因为我们这个工作是有时间前后顺序的&#xff0c;我们如果不排序的话我们一些截止时间晚的工作就无法得到最优报酬 #include<bits/stdc.h> using namespace std;#define int long long int t; int n; c…

看板项目之vue代码分析

目录&#xff1a; Q1、vue项目怎么实现的输入localhost&#xff1a;8080就能自动跳到index页面Q2、组合饼状图如何实现Q3、vue项目如何实现环境的切换Q4、vue怎么实现vue里面去调用js文件里面的函数 Q1、vue项目怎么实现的输入localhost&#xff1a;8080就能自动跳到index页面 …

数据结构——串

语言&#xff1a;C语言软件&#xff1a;Visual Studio 2022笔记书籍&#xff1a;数据结构——用C语言描述如有错误&#xff0c;感谢指正。若有侵权请联系博主 一、串的基本概念 子串&#xff1a;串中任意连续的字符组成的子序列称为该串的子串。 主串&#xff1a;包含子串的串称…

做一个能和你互动玩耍的智能机器人之三

内容节选自英特尔的开源项目openbot的body目录下diy下的readme&#xff0c;这是一个组装和连线方式的说明文档&#xff0c;接线需要配合firmware固件使用&#xff0c;固件代码的接线柱是对应的。 body目录内部十分丰富&#xff0c;主要介绍了这个项目的背景和硬件以及如何让他…

【SQL 新手教程 4/20】关系模型 --索引

&#x1f497; 关系数据库建立在关系模型上⭐ 关系模型本质上就是若干个存储数据的二维表 记录 (Record)&#xff1a; 表的每一行称为记录&#xff08;Record&#xff09;&#xff0c;记录是一个逻辑意义上的数据 字段 (Column)&#xff1a;表的每一列称为字段&#xff08;Colu…

echarts实现在市级行政区点击县级行政区,显示单个县级行政区地图数据

因需兼容ie&#xff0c;此处所有变量声明都用var。如无需支持&#xff0c;可另做let修改。 这里以常州市为例,我们可以去阿里云提供的地理工具去截取地图json数据DataV.GeoAtlas地理小工具系列 点击所选区域&#xff0c;右侧会对应显示json数据&#xff0c;再次点击右侧红框内…